├── .changeset ├── mighty-pets-report.md └── precious-eagle-cactus-fruit.md ├── .clang-format ├── .editorconfig ├── .env ├── .env.development ├── .env.production ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── 10-bug-report.yml │ ├── 20-feature-request.yml │ ├── 30-question.yml │ ├── 40-request-board.yml │ ├── 70-board-test-report.yml │ └── config.yml ├── actions │ ├── build-compilationdb │ │ └── action.yml │ ├── build-firmware │ │ └── action.yml │ ├── build-frontend │ │ └── action.yml │ ├── build-staticfs │ │ └── action.yml │ ├── cdn-bump-version │ │ └── action.yml │ ├── cdn-prepare │ │ └── action.yml │ ├── cdn-upload-firmware │ │ └── action.yml │ ├── cdn-upload-version-info │ │ └── action.yml │ └── merge-partitions │ │ └── action.yml ├── dependabot.yml ├── scripts │ ├── .gitignore │ ├── get-vars.js │ ├── package.json │ └── pnpm-lock.yaml └── workflows │ ├── ci-build.yml │ ├── codeql.yml │ ├── cpp-linter.yml │ └── get-vars.yml ├── .gitignore ├── .gitmodules ├── .prettierignore ├── .prettierrc ├── .python-version ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RELEASE.md ├── boards ├── Wemos-D1-Mini-ESP32.json ├── Wemos-Lolin-S2-Mini.json └── Wemos-Lolin-S3.json ├── certificates ├── README.md ├── cacrt_all.pem ├── gen_crt_bundle.py └── x509_crt_bundle ├── chips ├── ESP32-C3 │ └── 4MB │ │ └── merge-image.py ├── ESP32-S2 │ └── 4MB │ │ └── merge-image.py ├── ESP32-S3 │ └── 4MB │ │ └── merge-image.py ├── ESP32 │ └── 4MB │ │ └── merge-image.py ├── partitions_4MB.csv └── partitions_4MB_OTA.csv ├── frontend ├── .eslintignore ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── components.json ├── e2e │ └── demo.test.ts ├── eslint.config.js ├── package.json ├── playwright.config.ts ├── pnpm-lock.yaml ├── src │ ├── app.css │ ├── app.d.ts │ ├── app.html │ ├── demo.spec.ts │ ├── lib │ │ ├── MessageHandlers │ │ │ ├── WifiNetworkEventHandler.ts │ │ │ └── index.ts │ │ ├── Serializers │ │ │ ├── AccountLinkCommand.ts │ │ │ ├── SetEstopEnabledCommand.ts │ │ │ ├── SetEstopPinCommand.ts │ │ │ ├── SetRfTxPinCommand.ts │ │ │ ├── WifiNetworkConnectCommand.ts │ │ │ ├── WifiNetworkDisconnectCommand.ts │ │ │ ├── WifiNetworkForgetCommand.ts │ │ │ ├── WifiNetworkSaveCommand.ts │ │ │ └── WifiScanCommand.ts │ │ ├── TypeGuards │ │ │ └── BasicGuards.ts │ │ ├── WebSocketClient.ts │ │ ├── _fbs │ │ │ └── open-shock │ │ │ │ └── serialization │ │ │ │ ├── configuration.ts │ │ │ │ ├── configuration │ │ │ │ ├── backend-config.ts │ │ │ │ ├── captive-portal-config.ts │ │ │ │ ├── estop-config.ts │ │ │ │ ├── hub-config.ts │ │ │ │ ├── ota-update-channel.ts │ │ │ │ ├── ota-update-config.ts │ │ │ │ ├── ota-update-step.ts │ │ │ │ ├── rfconfig.ts │ │ │ │ ├── serial-input-config.ts │ │ │ │ ├── wi-fi-config.ts │ │ │ │ └── wi-fi-credentials.ts │ │ │ │ ├── gateway.ts │ │ │ │ ├── gateway │ │ │ │ ├── boot-status.ts │ │ │ │ ├── gateway-to-hub-message-payload.ts │ │ │ │ ├── gateway-to-hub-message.ts │ │ │ │ ├── hub-to-gateway-message-payload.ts │ │ │ │ ├── hub-to-gateway-message.ts │ │ │ │ ├── ota-update-failed.ts │ │ │ │ ├── ota-update-progress.ts │ │ │ │ ├── ota-update-request.ts │ │ │ │ ├── ota-update-started.ts │ │ │ │ ├── ping.ts │ │ │ │ ├── pong.ts │ │ │ │ ├── shocker-command-list.ts │ │ │ │ ├── shocker-command.ts │ │ │ │ ├── trigger-type.ts │ │ │ │ └── trigger.ts │ │ │ │ ├── local.ts │ │ │ │ ├── local │ │ │ │ ├── account-link-command-result.ts │ │ │ │ ├── account-link-command.ts │ │ │ │ ├── account-link-result-code.ts │ │ │ │ ├── account-unlink-command.ts │ │ │ │ ├── error-message.ts │ │ │ │ ├── hub-to-local-message-payload.ts │ │ │ │ ├── hub-to-local-message.ts │ │ │ │ ├── local-to-hub-message-payload.ts │ │ │ │ ├── local-to-hub-message.ts │ │ │ │ ├── ota-update-check-for-updates-command.ts │ │ │ │ ├── ota-update-handle-update-request-command.ts │ │ │ │ ├── ota-update-set-allow-backend-management-command.ts │ │ │ │ ├── ota-update-set-check-interval-command.ts │ │ │ │ ├── ota-update-set-domain-command.ts │ │ │ │ ├── ota-update-set-is-enabled-command.ts │ │ │ │ ├── ota-update-set-require-manual-approval-command.ts │ │ │ │ ├── ota-update-set-update-channel-command.ts │ │ │ │ ├── ota-update-start-update-command.ts │ │ │ │ ├── ready-message.ts │ │ │ │ ├── set-estop-enabled-command-result.ts │ │ │ │ ├── set-estop-enabled-command.ts │ │ │ │ ├── set-estop-pin-command-result.ts │ │ │ │ ├── set-estop-pin-command.ts │ │ │ │ ├── set-gpioresult-code.ts │ │ │ │ ├── set-rf-tx-pin-command-result.ts │ │ │ │ ├── set-rf-tx-pin-command.ts │ │ │ │ ├── wifi-got-ip-event.ts │ │ │ │ ├── wifi-lost-ip-event.ts │ │ │ │ ├── wifi-network-connect-command.ts │ │ │ │ ├── wifi-network-disconnect-command.ts │ │ │ │ ├── wifi-network-event.ts │ │ │ │ ├── wifi-network-forget-command.ts │ │ │ │ ├── wifi-network-save-command.ts │ │ │ │ ├── wifi-scan-command.ts │ │ │ │ └── wifi-scan-status-message.ts │ │ │ │ ├── types.ts │ │ │ │ └── types │ │ │ │ ├── firmware-boot-type.ts │ │ │ │ ├── ota-update-progress-task.ts │ │ │ │ ├── sem-ver.ts │ │ │ │ ├── shocker-command-type.ts │ │ │ │ ├── shocker-model-type.ts │ │ │ │ ├── wifi-auth-mode.ts │ │ │ │ ├── wifi-network-event-type.ts │ │ │ │ ├── wifi-network.ts │ │ │ │ └── wifi-scan-status.ts │ │ ├── components │ │ │ ├── AbsolutelySureButton.svelte │ │ │ ├── GpioPinSelector.svelte │ │ │ ├── Layout │ │ │ │ ├── Footer.svelte │ │ │ │ └── Header.svelte │ │ │ ├── LightSwitch.svelte │ │ │ ├── WiFiDetailsDialog.svelte │ │ │ ├── WiFiList.svelte │ │ │ └── ui │ │ │ │ ├── button │ │ │ │ ├── button.svelte │ │ │ │ └── index.ts │ │ │ │ ├── dialog │ │ │ │ ├── dialog-content.svelte │ │ │ │ ├── dialog-description.svelte │ │ │ │ ├── dialog-footer.svelte │ │ │ │ ├── dialog-header.svelte │ │ │ │ ├── dialog-overlay.svelte │ │ │ │ ├── dialog-title.svelte │ │ │ │ └── index.ts │ │ │ │ ├── dropdown-menu │ │ │ │ ├── dropdown-menu-checkbox-item.svelte │ │ │ │ ├── dropdown-menu-content.svelte │ │ │ │ ├── dropdown-menu-group-heading.svelte │ │ │ │ ├── dropdown-menu-item.svelte │ │ │ │ ├── dropdown-menu-label.svelte │ │ │ │ ├── dropdown-menu-radio-item.svelte │ │ │ │ ├── dropdown-menu-separator.svelte │ │ │ │ ├── dropdown-menu-shortcut.svelte │ │ │ │ ├── dropdown-menu-sub-content.svelte │ │ │ │ ├── dropdown-menu-sub-trigger.svelte │ │ │ │ └── index.ts │ │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── input.svelte │ │ │ │ ├── label │ │ │ │ ├── index.ts │ │ │ │ └── label.svelte │ │ │ │ ├── scroll-area │ │ │ │ ├── index.ts │ │ │ │ ├── scroll-area-scrollbar.svelte │ │ │ │ └── scroll-area.svelte │ │ │ │ └── sonner │ │ │ │ ├── index.ts │ │ │ │ └── sonner.svelte │ │ ├── index.ts │ │ ├── mappers │ │ │ └── ConfigMapper.ts │ │ ├── stores │ │ │ ├── ColorSchemeStore.ts │ │ │ ├── HubStateStore.ts │ │ │ ├── UsedPinsStore.ts │ │ │ └── index.ts │ │ ├── types │ │ │ ├── HubState.ts │ │ │ ├── WiFiNetwork.ts │ │ │ ├── WiFiNetworkGroup.ts │ │ │ └── index.ts │ │ └── utils.ts │ └── routes │ │ ├── +layout.svelte │ │ ├── +layout.ts │ │ └── +page.svelte ├── static │ ├── favicon.ico │ └── logo.svg ├── svelte.config.js ├── tailwind.config.ts ├── tsconfig.json └── vite.config.ts ├── include ├── AccountLinkResultCode.h ├── CaptivePortal.h ├── CaptivePortalInstance.h ├── Checksum.h ├── Chipset.h ├── CommandHandler.h ├── Common.h ├── Convert.h ├── Core.h ├── FirmwareBootType.h ├── FormatHelpers.h ├── GatewayClient.h ├── GatewayClientState.h ├── GatewayConnectionManager.h ├── Hashing.h ├── Logging.h ├── OtaUpdateChannel.h ├── OtaUpdateManager.h ├── OtaUpdateStep.h ├── PinPatternManager.h ├── RGBPatternManager.h ├── RateLimiter.h ├── ReadWriteMutex.h ├── SemVer.h ├── SetGPIOResultCode.h ├── ShockerCommandType.h ├── ShockerModelType.h ├── SimpleMutex.h ├── VisualStateManager.h ├── WebSocketDeFragger.h ├── WebSocketMessageType.h ├── config │ ├── BackendConfig.h │ ├── CaptivePortalConfig.h │ ├── Config.h │ ├── ConfigBase.h │ ├── EStopConfig.h │ ├── OtaUpdateConfig.h │ ├── RFConfig.h │ ├── RootConfig.h │ ├── SerialInputConfig.h │ ├── WiFiConfig.h │ ├── WiFiCredentials.h │ └── internal │ │ └── utils.h ├── estop │ ├── EStopManager.h │ └── EStopState.h ├── events │ └── Events.h ├── http │ ├── HTTPRequestManager.h │ └── JsonAPI.h ├── message_handlers │ ├── WebSocket.h │ └── impl │ │ ├── WSGateway.h │ │ └── WSLocal.h ├── radio │ ├── RFTransmitter.h │ └── rmt │ │ ├── CaiXianlinEncoder.h │ │ ├── MainEncoder.h │ │ ├── Petrainer998DREncoder.h │ │ ├── PetrainerEncoder.h │ │ ├── T330Encoder.h │ │ └── internal │ │ └── Shared.h ├── serial │ ├── SerialInputHandler.h │ └── command_handlers │ │ ├── CommandEntry.h │ │ ├── common.h │ │ └── index.h ├── serialization │ ├── CallbackFn.h │ ├── JsonAPI.h │ ├── JsonSerial.h │ ├── WSGateway.h │ ├── WSLocal.h │ └── _fbs │ │ ├── FirmwareBootType_generated.h │ │ ├── GatewayToHubMessage_generated.h │ │ ├── HubConfig_generated.h │ │ ├── HubToGatewayMessage_generated.h │ │ ├── HubToLocalMessage_generated.h │ │ ├── LocalToHubMessage_generated.h │ │ ├── OtaUpdateProgressTask_generated.h │ │ ├── SemVer_generated.h │ │ ├── ShockerCommandType_generated.h │ │ ├── ShockerModelType_generated.h │ │ ├── WifiAuthMode_generated.h │ │ ├── WifiNetworkEventType_generated.h │ │ ├── WifiNetwork_generated.h │ │ └── WifiScanStatus_generated.h ├── span.h ├── util │ ├── Base64Utils.h │ ├── CertificateUtils.h │ ├── DigitCounter.h │ ├── FnProxy.h │ ├── HexUtils.h │ ├── IPAddressUtils.h │ ├── PartitionUtils.h │ ├── StringUtils.h │ └── TaskUtils.h └── wifi │ ├── WiFiManager.h │ ├── WiFiNetwork.h │ ├── WiFiScanManager.h │ └── WiFiScanStatus.h ├── lib └── README ├── platformio.ini ├── requirements.txt ├── scripts ├── .gitignore ├── build_frontend.py ├── embed_env_vars.py ├── fetch_flatc.py ├── flatc ├── flatc.exe ├── generate_schemas.py ├── install_dependencies.py ├── merge_image.py ├── use_openshock_params.py └── utils │ ├── __init__.py │ ├── boardconf.py │ ├── conv.py │ ├── dotenv.py │ ├── pioenv.py │ ├── shorthands.py │ └── sysenv.py ├── src ├── CaptivePortal.cpp ├── CaptivePortalInstance.cpp ├── CommandHandler.cpp ├── CompatibilityChecks.cpp ├── Convert.cpp ├── EStopManager.cpp ├── GatewayClient.cpp ├── GatewayConnectionManager.cpp ├── OtaUpdateManager.cpp ├── PinPatternManager.cpp ├── RGBPatternManager.cpp ├── RateLimiter.cpp ├── ReadWriteMutex.cpp ├── SemVer.cpp ├── SimpleMutex.cpp ├── VisualStateManager.cpp ├── WebSocketDeFragger.cpp ├── config │ ├── BackendConfig.cpp │ ├── CaptivePortalConfig.cpp │ ├── Config.cpp │ ├── EStopConfig.cpp │ ├── OtaUpdateConfig.cpp │ ├── RFConfig.cpp │ ├── RootConfig.cpp │ ├── SerialInputConfig.cpp │ ├── WiFiConfig.cpp │ ├── WiFiCredentials.cpp │ └── internal │ │ └── utils.cpp ├── events │ └── Events.cpp ├── http │ ├── HTTPRequestManager.cpp │ └── JsonAPI.cpp ├── main.cpp ├── message_handlers │ └── websocket │ │ ├── Gateway.cpp │ │ ├── Local.cpp │ │ ├── gateway │ │ ├── OtaUpdateRequest.cpp │ │ ├── Ping.cpp │ │ ├── ShockerCommandList.cpp │ │ ├── Trigger.cpp │ │ └── _InvalidMessage.cpp │ │ └── local │ │ ├── AccountLinkCommand.cpp │ │ ├── AccountUnlinkCommand.cpp │ │ ├── OtaUpdateCheckForUpdatesCommand.cpp │ │ ├── OtaUpdateHandleUpdateRequestCommand.cpp │ │ ├── OtaUpdateSetAllowBackendManagementCommand.cpp │ │ ├── OtaUpdateSetCheckIntervalCommand.cpp │ │ ├── OtaUpdateSetDomainCommand.cpp │ │ ├── OtaUpdateSetIsEnabledCommand.cpp │ │ ├── OtaUpdateSetRequireManualApprovalCommand.cpp │ │ ├── OtaUpdateSetUpdateChannelCommand.cpp │ │ ├── OtaUpdateStartUpdateCommand.cpp │ │ ├── SetEStopEnabledCommand.cpp │ │ ├── SetEStopPinCommand.cpp │ │ ├── SetRfTxPinCommand.cpp │ │ ├── WiFiNetworkConnectCommand.cpp │ │ ├── WiFiNetworkDisconnectCommand.cpp │ │ ├── WiFiNetworkForgetCommand.cpp │ │ ├── WiFiNetworkSaveCommand.cpp │ │ ├── WiFiScanCommand.cpp │ │ └── _InvalidMessage.cpp ├── radio │ ├── RFTransmitter.cpp │ └── rmt │ │ ├── CaiXianlinEncoder.cpp │ │ ├── MainEncoder.cpp │ │ ├── Petrainer998DREncoder.cpp │ │ ├── PetrainerEncoder.cpp │ │ └── T330Encoder.cpp ├── serial │ ├── SerialInputHandler.cpp │ └── command_handlers │ │ ├── CommandEntry.cpp │ │ ├── authtoken.cpp │ │ ├── domain.cpp │ │ ├── echo.cpp │ │ ├── estop.cpp │ │ ├── factoryreset.cpp │ │ ├── hostname.cpp │ │ ├── jsonconfig.cpp │ │ ├── keepalive.cpp │ │ ├── networks.cpp │ │ ├── rawconfig.cpp │ │ ├── restart.cpp │ │ ├── rftransmit.cpp │ │ ├── rftxpin.cpp │ │ ├── sysinfo.cpp │ │ ├── validgpios.cpp │ │ └── version.cpp ├── serialization │ ├── JsonAPI.cpp │ ├── JsonSerial.cpp │ ├── WSGateway.cpp │ └── WSLocal.cpp ├── util │ ├── Base64Utils.cpp │ ├── CertificateUtils.cpp │ ├── DigitCounter.cpp │ ├── IPAddressUtils.cpp │ ├── ParitionUtils.cpp │ ├── StringUtils.cpp │ └── TaskUtils.cpp └── wifi │ ├── WiFiManager.cpp │ ├── WiFiNetwork.cpp │ └── WiFiScanManager.cpp └── test └── README /.changeset/mighty-pets-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'frontend': patch 3 | --- 4 | 5 | Improve authtoken handling in 401 responses 6 | -------------------------------------------------------------------------------- /.changeset/precious-eagle-cactus-fruit.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'firmware': patch 3 | --- 4 | 5 | fix: Serial now uses CRLF rather than just LF for better compatibility 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor config 2 | 3 | [*] 4 | 5 | # Indentation 6 | indent_style = space 7 | indent_size = 2 8 | 9 | # Line endings 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.py] 15 | indent_size = 4 16 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | OPENSHOCK_API_DOMAIN=api.openshock.app 2 | OPENSHOCK_FW_CDN_DOMAIN=firmware.openshock.org 3 | OPENSHOCK_FW_HOSTNAME=OpenShock 4 | OPENSHOCK_FW_AP_PREFIX=OpenShock- 5 | OPENSHOCK_URI_BUFFER_SIZE=256 6 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # Included by frontend/ (DO NOT RENAME THIS FILE!) 2 | # Intended for development builds (locally). 3 | LOG_LEVEL=VERBOSE 4 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # Included by frontend/ (DO NOT RENAME THIS FILE!) 2 | # Intended for all CI firmware builds. 3 | LOG_LEVEL=INFO 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.txt text eol=lf 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/20-feature-request.yml: -------------------------------------------------------------------------------- 1 | name: 'Feature Request' 2 | description: "Is there a feature you'd really like to see? Request it here." 3 | title: '[Feature] ' 4 | labels: ['type: enhancement'] 5 | projects: ['OpenShock/3'] 6 | 7 | body: 8 | 9 | - type: markdown 10 | attributes: 11 | value: | 12 | # Checklist 13 | 14 | - type: checkboxes 15 | id: checklist 16 | attributes: 17 | label: Pre-submission checklist 18 | description: | 19 | To prevent wasting your or our time, please fill out the below checklist before continuing. 20 | Thanks for understanding! 21 | options: 22 | - label: 'I checked that there is no other Feature Request describing my wish.' 23 | required: true 24 | - label: 'I checked that this feature is, in fact, not supported.' 25 | required: true 26 | - label: 'I accept that this issue may be closed if any of the above are found to be untrue.' 27 | required: true 28 | 29 | - type: markdown 30 | attributes: 31 | value: | 32 | # Describe the feature 33 | 34 | - type: textarea 35 | id: feature-request 36 | attributes: 37 | label: 'What should be possible that currently is not?' 38 | validations: 39 | required: true 40 | 41 | - type: markdown 42 | attributes: 43 | value: | 44 | # Anything else? 45 | 46 | - type: textarea 47 | id: anything-else 48 | attributes: 49 | label: 'Other remarks' 50 | validations: 51 | required: false 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/30-question.yml: -------------------------------------------------------------------------------- 1 | name: 'Question' 2 | description: "Can't figure something out? Feel free to ask." 3 | title: '[Question] ' 4 | labels: ['type: question'] 5 | projects: ['OpenShock/3'] 6 | 7 | body: 8 | 9 | - type: markdown 10 | attributes: 11 | value: | 12 | # Question 13 | 14 | - type: textarea 15 | id: question 16 | attributes: 17 | label: 'Ask your question!' 18 | placeholder: | 19 | How do I..? 20 | Is it possible to..? 21 | Are you planning to..? 22 | validations: 23 | required: true 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /.github/actions/build-compilationdb/action.yml: -------------------------------------------------------------------------------- 1 | name: build-compilationdb 2 | description: Builds the compilation database for the firmware for code analysis 3 | inputs: 4 | version: 5 | required: true 6 | description: 'Current firmware version' 7 | skip-checkout: 8 | description: 'If true, skips checkout' 9 | required: false 10 | default: false 11 | 12 | runs: 13 | using: composite 14 | steps: 15 | - uses: actions/checkout@v4 16 | if: ${{ !inputs.skip-checkout }} 17 | 18 | - uses: actions/cache@v4 19 | with: 20 | path: | 21 | ~/.platformio/platforms 22 | ~/.platformio/packages 23 | ~/.platformio/.cache 24 | key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini', 'requirements.txt') }} 25 | 26 | - uses: actions/setup-python@v5 27 | with: 28 | cache: 'pip' 29 | 30 | - name: Install python dependencies 31 | shell: bash 32 | run: pip install -r requirements.txt 33 | 34 | - name: Build firmware 35 | working-directory: . 36 | shell: bash 37 | run: pio run -e ci-build -t compiledb 38 | env: 39 | OPENSHOCK_FW_VERSION: ${{ inputs.version }} 40 | OPENSHOCK_FW_GIT_REF: ${{ github.ref }} 41 | OPENSHOCK_FW_GIT_COMMIT: ${{ github.sha }} 42 | OPENSHOCK_FW_BUILD_DATE: ${{ github.event.head_commit.timestamp }} -------------------------------------------------------------------------------- /.github/actions/build-firmware/action.yml: -------------------------------------------------------------------------------- 1 | name: build-firmware 2 | description: Builds the firmware partitions and uploads them as an artifact 3 | inputs: 4 | board: 5 | required: true 6 | description: 'Board name to build' 7 | version: 8 | required: true 9 | description: 'Current firmware version' 10 | skip-checkout: 11 | description: 'If true, skips checkout' 12 | required: false 13 | default: false 14 | 15 | runs: 16 | using: composite 17 | steps: 18 | - uses: actions/checkout@v4 19 | if: ${{ !inputs.skip-checkout }} 20 | 21 | - uses: actions/cache@v4 22 | with: 23 | path: | 24 | ~/.platformio/platforms 25 | ~/.platformio/packages 26 | ~/.platformio/.cache 27 | key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini', 'requirements.txt') }} 28 | 29 | - uses: actions/setup-python@v5 30 | with: 31 | cache: 'pip' 32 | 33 | - name: Install python dependencies 34 | shell: bash 35 | run: pip install -r requirements.txt 36 | 37 | - name: Build firmware 38 | working-directory: . 39 | shell: bash 40 | run: pio run -e ${{ inputs.board }} 41 | env: 42 | OPENSHOCK_FW_VERSION: ${{ inputs.version }} 43 | OPENSHOCK_FW_GIT_REF: ${{ github.ref }} 44 | OPENSHOCK_FW_GIT_COMMIT: ${{ github.sha }} 45 | OPENSHOCK_FW_BUILD_DATE: ${{ github.event.head_commit.timestamp }} 46 | 47 | - name: Upload build artifacts 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: firmware_build_${{ inputs.board }} 51 | path: .pio/build/${{ inputs.board }}/*.bin 52 | retention-days: 1 53 | if-no-files-found: error 54 | -------------------------------------------------------------------------------- /.github/actions/build-staticfs/action.yml: -------------------------------------------------------------------------------- 1 | name: build-staticfs 2 | description: Builds the static filesystem partition and uploads it as an artifact 3 | inputs: 4 | version: 5 | required: true 6 | description: 'Current firmware version' 7 | skip-checkout: 8 | description: 'If true, skips checkout' 9 | required: false 10 | default: false 11 | 12 | runs: 13 | using: composite 14 | steps: 15 | - uses: actions/checkout@v4 16 | if: ${{ !inputs.skip-checkout }} 17 | 18 | - uses: actions/cache@v4 19 | with: 20 | path: | 21 | ~/.platformio/platforms 22 | ~/.platformio/packages 23 | ~/.platformio/.cache 24 | key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini', 'requirements.txt') }} 25 | 26 | - uses: actions/setup-python@v5 27 | with: 28 | cache: 'pip' 29 | 30 | - name: Install dependencies 31 | shell: bash 32 | run: pip install -r requirements.txt 33 | 34 | - name: Download built frontend 35 | uses: actions/download-artifact@v4 36 | with: 37 | name: frontend 38 | path: frontend/build/ 39 | 40 | - name: Build filesystem partition 41 | shell: bash 42 | run: pio run --target buildfs -e fs 43 | env: 44 | OPENSHOCK_FW_VERSION: ${{ inputs.version }} 45 | OPENSHOCK_FW_GIT_REF: ${{ github.ref }} 46 | OPENSHOCK_FW_GIT_COMMIT: ${{ github.sha }} 47 | OPENSHOCK_FW_BUILD_DATE: ${{ github.event.head_commit.timestamp }} 48 | 49 | - name: Rename partition binary 50 | shell: bash 51 | run: mv .pio/build/fs/littlefs.bin staticfs.bin 52 | 53 | - name: Upload internal filesystem artifact 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: firmware_staticfs 57 | path: staticfs.bin 58 | retention-days: 1 59 | if-no-files-found: error 60 | -------------------------------------------------------------------------------- /.github/actions/cdn-bump-version/action.yml: -------------------------------------------------------------------------------- 1 | name: cdn-bump-version 2 | description: Uploads version file to CDN 3 | inputs: 4 | bunny-stor-hostname: 5 | description: Bunny SFTP Hostname 6 | required: true 7 | bunny-stor-username: 8 | description: Bunny SFTP Username 9 | required: true 10 | bunny-stor-password: 11 | description: Bunny SFTP Password 12 | required: true 13 | bunny-api-key: 14 | description: Bunny API key 15 | required: true 16 | bunny-cdn-url: 17 | description: Bunny pull zone base url e.g. https://firmware.openshock.org (no trailing slash) 18 | required: true 19 | version: 20 | description: 'Version of the release' 21 | required: true 22 | release-channel: 23 | description: 'Release channel that describes this upload' 24 | required: true 25 | 26 | runs: 27 | using: composite 28 | steps: 29 | - name: Prepare Upload Folder 30 | shell: bash 31 | run: | 32 | mkdir -p upload 33 | echo "${{ inputs.version }}" >> upload/version-${{ inputs.release-channel }}.txt 34 | 35 | - name: Upload version file 36 | uses: milanmk/actions-file-deployer@master 37 | with: 38 | remote-protocol: "sftp" 39 | remote-host: "${{ inputs.bunny-stor-hostname }}" 40 | remote-user: "${{ inputs.bunny-stor-username }}" 41 | remote-password: "${{ inputs.bunny-stor-password }}" 42 | remote-path: "/" 43 | local-path: "upload" 44 | sync: "full" 45 | 46 | - name: Purge CDN cache 47 | shell: bash 48 | run: | 49 | curl -X POST "https://api.bunny.net/purge?url=${{ inputs.bunny-cdn-url }}/version-${{ inputs.release-channel }}.txt" \ 50 | -H "Content-Type: application/json" \ 51 | -H "AccessKey: ${{ inputs.bunny-api-key }}" -------------------------------------------------------------------------------- /.github/actions/cdn-prepare/action.yml: -------------------------------------------------------------------------------- 1 | name: cdn-prepare 2 | description: Bunny sshpass and knowhosts setup 3 | inputs: 4 | bunny-ssh-knownhosts: 5 | description: Bunny SFTP Hostname 6 | required: true 7 | 8 | runs: 9 | using: composite 10 | steps: 11 | - name: Install sshpass 12 | shell: bash 13 | run: sudo apt-get install -y sshpass 14 | 15 | - name: Configure known hosts 16 | shell: bash 17 | run: | 18 | mkdir -p ~/.ssh 19 | echo "${{ inputs.bunny-ssh-knownhosts }}" >> ~/.ssh/known_hosts 20 | 21 | -------------------------------------------------------------------------------- /.github/actions/cdn-upload-version-info/action.yml: -------------------------------------------------------------------------------- 1 | name: cdn-upload-version-info 2 | description: Uploads version specific info to CDN 3 | inputs: 4 | bunny-stor-hostname: 5 | description: Bunny SFTP Hostname 6 | required: true 7 | bunny-stor-username: 8 | description: Bunny SFTP Username 9 | required: true 10 | bunny-stor-password: 11 | description: Bunny SFTP Password 12 | required: true 13 | fw-version: 14 | description: Firmware version 15 | required: true 16 | release-channel: 17 | description: 'Release channel that describes this upload' 18 | required: true 19 | boards: 20 | description: 'List of boards, separated by newlines' 21 | required: true 22 | 23 | runs: 24 | using: composite 25 | steps: 26 | 27 | - name: Prepare Upload Folder 28 | shell: bash 29 | run: | 30 | rm -rf upload/ 31 | mkdir -p upload/ 32 | 33 | - name: Create boards.txt 34 | shell: bash 35 | run: | 36 | echo -e '${{ inputs.boards }}' >> upload/boards.txt 37 | 38 | - name: Upload artifacts to CDN 39 | uses: milanmk/actions-file-deployer@master 40 | with: 41 | remote-protocol: "sftp" 42 | remote-host: "${{ inputs.bunny-stor-hostname }}" 43 | remote-user: "${{ inputs.bunny-stor-username }}" 44 | remote-password: "${{ inputs.bunny-stor-password }}" 45 | remote-path: "/${{ inputs.fw-version }}/" 46 | local-path: "upload" 47 | sync: "full" -------------------------------------------------------------------------------- /.github/actions/merge-partitions/action.yml: -------------------------------------------------------------------------------- 1 | name: merge-partitions 2 | description: Merges multiple partitions into a single flashable binary 3 | inputs: 4 | version: 5 | description: 'Version of the firmware' 6 | required: true 7 | board: 8 | description: 'Board name to merge partitions for' 9 | required: true 10 | skip-checkout: 11 | description: 'If true, skips checkout' 12 | required: false 13 | default: false 14 | 15 | runs: 16 | using: composite 17 | steps: 18 | - uses: actions/checkout@v4 19 | if: ${{ !inputs.skip-checkout }} 20 | with: 21 | sparse-checkout: | 22 | scripts 23 | boards 24 | chips 25 | 26 | - uses: actions/setup-python@v5 27 | with: 28 | cache: 'pip' 29 | 30 | - name: Install dependencies 31 | shell: bash 32 | run: pip install -r requirements.txt 33 | 34 | - name: Download static filesystem partition 35 | uses: actions/download-artifact@v4 36 | with: 37 | name: firmware_staticfs 38 | 39 | - name: Download firmware partitions 40 | uses: actions/download-artifact@v4 41 | with: 42 | name: firmware_build_${{ inputs.board }} 43 | 44 | - name: Merge partitions 45 | shell: bash 46 | run: | 47 | python scripts/merge_image.py ${{ inputs.board }} 48 | mv merged.bin OpenShock_${{ inputs.board }}_${{ inputs.version }}.bin 49 | 50 | - name: Upload merged firmware binary 51 | uses: actions/upload-artifact@v4 52 | with: 53 | name: firmware_merged_${{ inputs.board }} 54 | path: OpenShock_${{ inputs.board }}_*.bin 55 | retention-days: 7 56 | if-no-files-found: error 57 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Daily checks for updates in Github Actions (CI/CD workflows) 9 | - package-ecosystem: 'github-actions' 10 | directory: '/' 11 | schedule: 12 | interval: 'weekly' 13 | day: 'monday' 14 | time: '06:00' 15 | groups: 16 | ci-cd: 17 | patterns: 18 | - '*' # Group all updates together 19 | 20 | # Daily checks for npm package updates in CI-CD scripts 21 | - package-ecosystem: 'npm' 22 | directory: '/.github/scripts' 23 | schedule: 24 | interval: 'weekly' 25 | day: 'monday' 26 | time: '06:00' 27 | groups: 28 | ci-cd: 29 | patterns: 30 | - '*' # Group all updates together 31 | 32 | # Daily checks for pip package updates in build system scripts 33 | - package-ecosystem: 'pip' 34 | directory: '/' 35 | schedule: 36 | interval: 'weekly' 37 | day: 'monday' 38 | time: '06:00' 39 | groups: 40 | build-system: 41 | patterns: 42 | - '*' # Group all updates together 43 | 44 | # Daily checks for npm package updates in frontend 45 | - package-ecosystem: 'npm' 46 | directory: '/frontend' 47 | schedule: 48 | interval: 'weekly' 49 | day: 'monday' 50 | time: '06:00' 51 | groups: 52 | frontend: 53 | patterns: 54 | - '*' # Group all updates together 55 | -------------------------------------------------------------------------------- /.github/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.github/scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-variables", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "main": "src/index.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "dependencies": { 11 | "@actions/core": "^1.10.1", 12 | "@actions/github": "^6.0.1", 13 | "ini": "^5.0.0", 14 | "semver": "^7.7.2" 15 | }, 16 | "engines": { 17 | "node": "^22.14.0", 18 | "pnpm": "^10.6.4" 19 | }, 20 | "volta": { 21 | "node": "22.14.0" 22 | }, 23 | "packageManager": "pnpm@10.6.4" 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/cpp-linter.yml: -------------------------------------------------------------------------------- 1 | name: cpp-linter 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize] 6 | paths: ['**.c', '**.h', '**.cpp', '**.hpp', '.clang-format'] 7 | push: 8 | paths: ['**.c', '**.h', '**.cpp', '**.hpp', '.clang-format'] 9 | workflow_dispatch: # Manually invoked by user. 10 | 11 | jobs: 12 | get-vars: 13 | uses: ./.github/workflows/get-vars.yml 14 | 15 | cpp-linter: 16 | name: C/C++ Linter 17 | runs-on: ubuntu-latest 18 | needs: get-vars 19 | 20 | permissions: 21 | contents: write 22 | pull-requests: write 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v4 27 | 28 | - uses: ./.github/actions/build-compilationdb 29 | with: 30 | version: 0.0.0-test+build # Doesn't matter, just need the compilation database 31 | skip-checkout: true 32 | 33 | - uses: cpp-linter/cpp-linter-action@v2 34 | id: linter 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | with: 38 | style: file 39 | version: 18 40 | lines-changed-only: diff 41 | thread-comments: true 42 | file-annotations: false 43 | 44 | - name: Fail fast?! 45 | if: steps.linter.outputs.checks-failed > 0 46 | run: | 47 | echo "Some files failed the linting checks!" 48 | # for actual deployment 49 | # run: exit 1 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/extensions.json 5 | .vscode/launch.json 6 | .vscode/ipch 7 | *.bin 8 | 9 | # data/www is autogenerated by the build process, ignore it: 10 | data/www 11 | !data/**/*.bin 12 | 13 | # ignore local config files 14 | *.local 15 | 16 | # ignore generated files 17 | .vs 18 | packages 19 | *.sln 20 | compile_commands.json -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "schemas"] 2 | path = schemas 3 | url = https://github.com/OpenShock/flatbuffers-schemas 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | **/node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | **/.env 7 | **/.env.* 8 | 9 | # Ignore files for PNPM, NPM and YARN 10 | **/pnpm-lock.yaml 11 | **/package-lock.json 12 | **/yarn.lock 13 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": true, 4 | "useTabs": false, 5 | "singleQuote": true, 6 | "trailingComma": "es5", 7 | "printWidth": 256 8 | } 9 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.12.5 2 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | ## Flashing the firmware 2 | 3 | Download `OpenShock_[board]_[version].bin` and flash it to your microcontroller: 4 | 5 | ```bash 6 | esptool write_flash 0x0 OpenShock_[board]_[version].bin 7 | ``` 8 | -------------------------------------------------------------------------------- /boards/Wemos-D1-Mini-ESP32.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "arduino": { 4 | "ldscript": "esp32_out.ld" 5 | }, 6 | "core": "esp32", 7 | "extra_flags": "-DARDUINO_D1_MINI32", 8 | "f_cpu": "240000000L", 9 | "f_flash": "40000000L", 10 | "flash_mode": "dio", 11 | "mcu": "esp32", 12 | "variant": "d1_mini32" 13 | }, 14 | "connectivity": [ 15 | "wifi", 16 | "bluetooth", 17 | "ethernet", 18 | "can" 19 | ], 20 | "debug": { 21 | "openocd_board": "esp-wroom-32.cfg" 22 | }, 23 | "frameworks": [ 24 | "arduino", 25 | "espidf" 26 | ], 27 | "name": "WEMOS D1 MINI ESP32", 28 | "upload": { 29 | "flash_size": "4MB", 30 | "maximum_ram_size": 327680, 31 | "maximum_size": 4194304, 32 | "require_upload_port": true, 33 | "speed": 460800 34 | }, 35 | "url": "https://www.wemos.cc", 36 | "vendor": "WEMOS" 37 | } -------------------------------------------------------------------------------- /boards/Wemos-Lolin-S2-Mini.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "arduino": { 4 | "ldscript": "esp32s2_out.ld" 5 | }, 6 | "core": "esp32", 7 | "extra_flags": [ 8 | "-DARDUINO_LOLIN_S2_MINI", 9 | "-DBOARD_HAS_PSRAM", 10 | "-DARDUINO_USB_CDC_ON_BOOT=1" 11 | ], 12 | "f_cpu": "240000000L", 13 | "f_flash": "80000000L", 14 | "flash_mode": "dio", 15 | "hwids": [ 16 | [ 17 | "0X303A", 18 | "0x80C2" 19 | ] 20 | ], 21 | "mcu": "esp32s2", 22 | "variant": "lolin_s2_mini" 23 | }, 24 | "connectivity": [ 25 | "wifi" 26 | ], 27 | "debug": { 28 | "openocd_target": "esp32s2.cfg" 29 | }, 30 | "frameworks": [ 31 | "arduino", 32 | "espidf" 33 | ], 34 | "name": "WEMOS LOLIN S2 Mini", 35 | "upload": { 36 | "flash_size": "4MB", 37 | "maximum_ram_size": 327680, 38 | "maximum_size": 4194304, 39 | "use_1200bps_touch": true, 40 | "wait_for_upload_port": true, 41 | "require_upload_port": true, 42 | "speed": 921600 43 | }, 44 | "url": "https://www.wemos.cc/en/latest/s2/s2_mini.html", 45 | "vendor": "WEMOS" 46 | } -------------------------------------------------------------------------------- /boards/Wemos-Lolin-S3.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "arduino": { 4 | "ldscript": "esp32s3_out.ld", 5 | "partitions": "default_16MB.csv", 6 | "memory_type": "qio_opi" 7 | }, 8 | "core": "esp32", 9 | "extra_flags": ["-DBOARD_HAS_PSRAM", "-DARDUINO_LOLIN_S3", "-DARDUINO_USB_MODE=1"], 10 | "f_cpu": "240000000L", 11 | "f_flash": "80000000L", 12 | "flash_mode": "qio", 13 | "hwids": [["0x303A", "0x1001"]], 14 | "mcu": "esp32s3", 15 | "variant": "lolin_s3" 16 | }, 17 | "connectivity": ["wifi", "bluetooth"], 18 | "debug": { 19 | "openocd_target": "esp32s3.cfg" 20 | }, 21 | "frameworks": ["arduino", "espidf"], 22 | "name": "WEMOS LOLIN S3", 23 | "upload": { 24 | "flash_size": "16MB", 25 | "maximum_ram_size": 327680, 26 | "maximum_size": 16777216, 27 | "use_1200bps_touch": true, 28 | "wait_for_upload_port": true, 29 | "require_upload_port": true, 30 | "speed": 460800 31 | }, 32 | "url": "https://www.wemos.cc/en/latest/s3/index.html", 33 | "vendor": "WEMOS" 34 | } 35 | -------------------------------------------------------------------------------- /certificates/README.md: -------------------------------------------------------------------------------- 1 | # Certificates used for HTTPS 2 | 3 | These certificates are fetched from [CURL's website](https://curl.se/docs/caextract.html) and are used for HTTPS connections. 4 | 5 | ## How to update 6 | 7 | 1. Download the latest version of the certificate bundle along with its sha256 from [CURL's website](https://curl.se/docs/caextract.html). 8 | 2. Replace the `cacrt_all.pem` file in this directory with the new one. 9 | 3. Run `gen_crt_bundle.py` to generate the new `x509_crt_bundle` file. 10 | 4. Commit the changes. 11 | 12 | ## References 13 | 14 | - [CURL's website](https://curl.se/docs/caextract.html) 15 | - [Espressif's documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/esp_crt_bundle.html) 16 | - [WiFiClientSecure Documentation](https://github.com/espressif/arduino-esp32/tree/master/libraries/WiFiClientSecure#using-a-bundle-of-root-certificate-authority-certificates) 17 | -------------------------------------------------------------------------------- /certificates/x509_crt_bundle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenShock/Firmware/70dd3619e14ff939025a137e5db1defdd00131bc/certificates/x509_crt_bundle -------------------------------------------------------------------------------- /chips/ESP32-C3/4MB/merge-image.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | import esptool 4 | 5 | # fmt: off 6 | # Note: Bootloader for esp32-s3 starts at 0x0000, unlike several other ESP32 variants that start at 0x1000. 7 | esptool.main([ 8 | '--chip', 'esp32s3', 9 | 'merge_bin', '-o', 'merged.bin', 10 | '--flash_size', '4MB', 11 | '0x0', './bootloader.bin', 12 | '0x8000', './partitions.bin', 13 | '0x10000', './app.bin', 14 | '0x353000', './staticfs.bin' # This is littlefs.bin, the github CI/CD pipeline renames it to staticfs.bin 15 | ]) 16 | # fmt: on 17 | -------------------------------------------------------------------------------- /chips/ESP32-S2/4MB/merge-image.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | import esptool 4 | 5 | # fmt: off 6 | esptool.main([ 7 | '--chip', 'esp32', 8 | 'merge_bin', '-o', 'merged.bin', 9 | '--flash_size', '4MB', 10 | '0x1000', './bootloader.bin', 11 | '0x8000', './partitions.bin', 12 | '0x10000', './app.bin', 13 | '0x353000', './staticfs.bin' # This is littlefs.bin, the github CI/CD pipeline renames it to staticfs.bin 14 | ]) 15 | # fmt: on 16 | -------------------------------------------------------------------------------- /chips/ESP32-S3/4MB/merge-image.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | import esptool 4 | 5 | # fmt: off 6 | # Note: Bootloader for esp32-s3 starts at 0x0000, unlike several other ESP32 variants that start at 0x1000. 7 | esptool.main([ 8 | '--chip', 'esp32s3', 9 | 'merge_bin', '-o', 'merged.bin', 10 | '--flash_size', '4MB', 11 | '0x0', './bootloader.bin', 12 | '0x8000', './partitions.bin', 13 | '0x10000', './app.bin', 14 | '0x353000', './staticfs.bin' # This is littlefs.bin, the github CI/CD pipeline renames it to staticfs.bin 15 | ]) 16 | # fmt: on 17 | -------------------------------------------------------------------------------- /chips/ESP32/4MB/merge-image.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | import esptool 4 | 5 | # fmt: off 6 | esptool.main([ 7 | '--chip', 'esp32', 8 | 'merge_bin', '-o', 'merged.bin', 9 | '--flash_size', '4MB', 10 | '0x1000', './bootloader.bin', 11 | '0x8000', './partitions.bin', 12 | '0x10000', './app.bin', 13 | '0x353000', './staticfs.bin' # This is littlefs.bin, the github CI/CD pipeline renames it to staticfs.bin 14 | ]) 15 | # fmt: on 16 | -------------------------------------------------------------------------------- /chips/partitions_4MB.csv: -------------------------------------------------------------------------------- 1 | # CURRENTLY NOT USED - KEPT FOR REFERENCE 2 | # OpenShock 4MB Partition Table - without OTA 3 | # Name, Type, SubType, Offset, Size, Flags 4 | # nvs, data, nvs, 0x009000, 0x005000, 5 | # otadata, data, ota, 0x00e000, 0x002000, 6 | # app0, app, ota_0, 0x010000, 0x340000, 7 | # config, data, spiffs, 0x350000, 0x003000, 8 | # static0, data, spiffs, 0x353000, 0x09D000, 9 | # coredump, data, coredump, 0x3F0000, 0x010000, -------------------------------------------------------------------------------- /chips/partitions_4MB_OTA.csv: -------------------------------------------------------------------------------- 1 | # OpenShock 4MB Partition Table - with OTA 2 | # Name, Type, SubType, Offset, Size, Flags 3 | nvs, data, nvs, 0x009000, 0x005000, 4 | otadata, data, ota, 0x00e000, 0x002000, 5 | app0, app, ota_0, 0x010000, 0x1A0000, 6 | app1, app, ota_1, 0x1B0000, 0x1A0000, 7 | config, data, spiffs, 0x350000, 0x003000, 8 | static0, data, spiffs, 0x353000, 0x09D000, 9 | coredump, data, coredump, 0x3F0000, 0x010000, -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Package Managers 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | 15 | # Generated or imported files 16 | /components.json 17 | /src/lib/_fbs 18 | /src/lib/components/ui 19 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | test-results 2 | node_modules 3 | 4 | # Output 5 | .output 6 | .vercel 7 | .netlify 8 | .wrangler 9 | /.svelte-kit 10 | /build 11 | 12 | # OS 13 | .DS_Store 14 | Thumbs.db 15 | 16 | # Env 17 | .env 18 | .env.* 19 | !.env.example 20 | !.env.test 21 | 22 | # Vite 23 | vite.config.js.timestamp-* 24 | vite.config.ts.timestamp-* 25 | -------------------------------------------------------------------------------- /frontend/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /frontend/.nvmrc: -------------------------------------------------------------------------------- 1 | 22.14.0 2 | -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Package Managers 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | 15 | # Generated or imported files 16 | /components.json 17 | /src/lib/_fbs 18 | /src/lib/components/ui 19 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": true, 4 | "useTabs": false, 5 | "singleQuote": true, 6 | "trailingComma": "es5", 7 | "printWidth": 100, 8 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 9 | "overrides": [ 10 | { "files": "*.svelte", "options": { "parser": "svelte" } }, 11 | { "files": "package*.json", "options": { "tabWidth": 2, "useTabs": true } } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://next.shadcn-svelte.com/schema.json", 3 | "style": "default", 4 | "tailwind": { 5 | "config": "tailwind.config.ts", 6 | "css": "src\\app.css", 7 | "baseColor": "neutral" 8 | }, 9 | "aliases": { 10 | "components": "$lib/components", 11 | "utils": "$lib/utils", 12 | "ui": "$lib/components/ui", 13 | "hooks": "$lib/hooks" 14 | }, 15 | "typescript": true, 16 | "registry": "https://next.shadcn-svelte.com/registry" 17 | } 18 | -------------------------------------------------------------------------------- /frontend/e2e/demo.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test('home page has expected h3', async ({ page }) => { 4 | await page.goto('/'); 5 | await expect(page.locator('h3')).toBeVisible(); 6 | }); 7 | -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import prettier from 'eslint-config-prettier'; 2 | import js from '@eslint/js'; 3 | import { includeIgnoreFile } from '@eslint/compat'; 4 | import svelte from 'eslint-plugin-svelte'; 5 | import globals from 'globals'; 6 | import { fileURLToPath } from 'node:url'; 7 | import ts from 'typescript-eslint'; 8 | const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); 9 | 10 | export default ts.config( 11 | includeIgnoreFile(gitignorePath), 12 | js.configs.recommended, 13 | ...ts.configs.recommended, 14 | ...svelte.configs['flat/recommended'], 15 | prettier, 16 | ...svelte.configs['flat/prettier'], 17 | { 18 | languageOptions: { 19 | globals: { 20 | ...globals.browser, 21 | ...globals.node, 22 | }, 23 | }, 24 | }, 25 | { 26 | files: ['**/*.svelte'], 27 | 28 | languageOptions: { 29 | parserOptions: { 30 | parser: ts.parser, 31 | ecmaVersion: 2020, 32 | }, 33 | }, 34 | } 35 | ); 36 | -------------------------------------------------------------------------------- /frontend/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@playwright/test'; 2 | 3 | export default defineConfig({ 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 4173, 7 | }, 8 | 9 | testDir: 'e2e', 10 | }); 11 | -------------------------------------------------------------------------------- /frontend/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://svelte.dev/docs/kit/types#app.d.ts 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /frontend/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OpenShock Captive Portal 8 | %sveltekit.head% 9 | 10 | 11 |
%sveltekit.body%
12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/demo.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /frontend/src/lib/Serializers/AccountLinkCommand.ts: -------------------------------------------------------------------------------- 1 | import { Builder as FlatbufferBuilder } from 'flatbuffers'; 2 | import { LocalToHubMessage } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message'; 3 | import { LocalToHubMessagePayload } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message-payload'; 4 | import { AccountLinkCommand } from '$lib/_fbs/open-shock/serialization/local/account-link-command'; 5 | 6 | export function SerializeAccountLinkCommand(linkCode: string): Uint8Array { 7 | const fbb = new FlatbufferBuilder(64); 8 | 9 | const linkCodeOffset = fbb.createString(linkCode); 10 | 11 | const cmdOffset = AccountLinkCommand.createAccountLinkCommand(fbb, linkCodeOffset); 12 | 13 | const payloadOffset = LocalToHubMessage.createLocalToHubMessage( 14 | fbb, 15 | LocalToHubMessagePayload.AccountLinkCommand, 16 | cmdOffset 17 | ); 18 | 19 | fbb.finish(payloadOffset); 20 | 21 | return fbb.asUint8Array(); 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/lib/Serializers/SetEstopEnabledCommand.ts: -------------------------------------------------------------------------------- 1 | import { Builder as FlatbufferBuilder } from 'flatbuffers'; 2 | import { LocalToHubMessage } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message'; 3 | import { LocalToHubMessagePayload } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message-payload'; 4 | import { SetEstopEnabledCommand } from '$lib/_fbs/open-shock/serialization/local/set-estop-enabled-command'; 5 | 6 | export function SerializeSetEstopEnabledCommand(enabled: boolean): Uint8Array { 7 | const fbb = new FlatbufferBuilder(64); 8 | 9 | const cmdOffset = SetEstopEnabledCommand.createSetEstopEnabledCommand(fbb, enabled); 10 | 11 | const payloadOffset = LocalToHubMessage.createLocalToHubMessage( 12 | fbb, 13 | LocalToHubMessagePayload.SetEstopEnabledCommand, 14 | cmdOffset 15 | ); 16 | 17 | fbb.finish(payloadOffset); 18 | 19 | return fbb.asUint8Array(); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/lib/Serializers/SetEstopPinCommand.ts: -------------------------------------------------------------------------------- 1 | import { Builder as FlatbufferBuilder } from 'flatbuffers'; 2 | import { LocalToHubMessage } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message'; 3 | import { LocalToHubMessagePayload } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message-payload'; 4 | import { SetEstopPinCommand } from '$lib/_fbs/open-shock/serialization/local/set-estop-pin-command'; 5 | 6 | export function SerializeSetEstopPinCommand(pin: number): Uint8Array { 7 | const fbb = new FlatbufferBuilder(64); 8 | 9 | const cmdOffset = SetEstopPinCommand.createSetEstopPinCommand(fbb, pin); 10 | 11 | const payloadOffset = LocalToHubMessage.createLocalToHubMessage( 12 | fbb, 13 | LocalToHubMessagePayload.SetEstopPinCommand, 14 | cmdOffset 15 | ); 16 | 17 | fbb.finish(payloadOffset); 18 | 19 | return fbb.asUint8Array(); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/lib/Serializers/SetRfTxPinCommand.ts: -------------------------------------------------------------------------------- 1 | import { Builder as FlatbufferBuilder } from 'flatbuffers'; 2 | import { LocalToHubMessage } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message'; 3 | import { LocalToHubMessagePayload } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message-payload'; 4 | import { SetRfTxPinCommand } from '$lib/_fbs/open-shock/serialization/local/set-rf-tx-pin-command'; 5 | 6 | export function SerializeSetRfTxPinCommand(pin: number): Uint8Array { 7 | const fbb = new FlatbufferBuilder(64); 8 | 9 | const cmdOffset = SetRfTxPinCommand.createSetRfTxPinCommand(fbb, pin); 10 | 11 | const payloadOffset = LocalToHubMessage.createLocalToHubMessage( 12 | fbb, 13 | LocalToHubMessagePayload.SetRfTxPinCommand, 14 | cmdOffset 15 | ); 16 | 17 | fbb.finish(payloadOffset); 18 | 19 | return fbb.asUint8Array(); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/lib/Serializers/WifiNetworkConnectCommand.ts: -------------------------------------------------------------------------------- 1 | import { Builder as FlatbufferBuilder } from 'flatbuffers'; 2 | import { LocalToHubMessage } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message'; 3 | import { LocalToHubMessagePayload } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message-payload'; 4 | import { WifiNetworkConnectCommand } from '$lib/_fbs/open-shock/serialization/local/wifi-network-connect-command'; 5 | 6 | export function SerializeWifiNetworkConnectCommand(ssid: string): Uint8Array { 7 | const fbb = new FlatbufferBuilder(128); 8 | 9 | const ssidOffset = fbb.createString(ssid); 10 | 11 | const cmdOffset = WifiNetworkConnectCommand.createWifiNetworkConnectCommand(fbb, ssidOffset); 12 | 13 | const payloadOffset = LocalToHubMessage.createLocalToHubMessage( 14 | fbb, 15 | LocalToHubMessagePayload.WifiNetworkConnectCommand, 16 | cmdOffset 17 | ); 18 | 19 | fbb.finish(payloadOffset); 20 | 21 | return fbb.asUint8Array(); 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/lib/Serializers/WifiNetworkDisconnectCommand.ts: -------------------------------------------------------------------------------- 1 | import { Builder as FlatbufferBuilder } from 'flatbuffers'; 2 | import { LocalToHubMessage } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message'; 3 | import { LocalToHubMessagePayload } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message-payload'; 4 | import { WifiNetworkDisconnectCommand } from '$lib/_fbs/open-shock/serialization/local/wifi-network-disconnect-command'; 5 | 6 | export function SerializeWifiNetworkDisconnectCommand(): Uint8Array { 7 | const fbb = new FlatbufferBuilder(32); 8 | 9 | const cmdOffset = WifiNetworkDisconnectCommand.createWifiNetworkDisconnectCommand(fbb, true); 10 | 11 | const payloadOffset = LocalToHubMessage.createLocalToHubMessage( 12 | fbb, 13 | LocalToHubMessagePayload.WifiNetworkDisconnectCommand, 14 | cmdOffset 15 | ); 16 | 17 | fbb.finish(payloadOffset); 18 | 19 | return fbb.asUint8Array(); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/lib/Serializers/WifiNetworkForgetCommand.ts: -------------------------------------------------------------------------------- 1 | import { Builder as FlatbufferBuilder } from 'flatbuffers'; 2 | import { LocalToHubMessage } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message'; 3 | import { LocalToHubMessagePayload } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message-payload'; 4 | import { WifiNetworkForgetCommand } from '$lib/_fbs/open-shock/serialization/local/wifi-network-forget-command'; 5 | 6 | export function SerializeWifiNetworkForgetCommand(ssid: string): Uint8Array { 7 | const fbb = new FlatbufferBuilder(128); 8 | 9 | const ssidOffset = fbb.createString(ssid); 10 | 11 | const cmdOffset = WifiNetworkForgetCommand.createWifiNetworkForgetCommand(fbb, ssidOffset); 12 | 13 | const payloadOffset = LocalToHubMessage.createLocalToHubMessage( 14 | fbb, 15 | LocalToHubMessagePayload.WifiNetworkForgetCommand, 16 | cmdOffset 17 | ); 18 | 19 | fbb.finish(payloadOffset); 20 | 21 | return fbb.asUint8Array(); 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/lib/Serializers/WifiNetworkSaveCommand.ts: -------------------------------------------------------------------------------- 1 | import { Builder as FlatbufferBuilder } from 'flatbuffers'; 2 | import { LocalToHubMessage } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message'; 3 | import { LocalToHubMessagePayload } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message-payload'; 4 | import { WifiNetworkSaveCommand } from '$lib/_fbs/open-shock/serialization/local/wifi-network-save-command'; 5 | 6 | export function SerializeWifiNetworkSaveCommand( 7 | ssid: string, 8 | password: string | null, 9 | connect: boolean 10 | ): Uint8Array { 11 | const fbb = new FlatbufferBuilder(128); 12 | 13 | const ssidOffset = fbb.createString(ssid); 14 | let passwordOffset = 0; 15 | if (password) { 16 | passwordOffset = fbb.createString(password); 17 | } 18 | 19 | const cmdOffset = WifiNetworkSaveCommand.createWifiNetworkSaveCommand( 20 | fbb, 21 | ssidOffset, 22 | passwordOffset, 23 | connect 24 | ); 25 | 26 | const payloadOffset = LocalToHubMessage.createLocalToHubMessage( 27 | fbb, 28 | LocalToHubMessagePayload.WifiNetworkSaveCommand, 29 | cmdOffset 30 | ); 31 | 32 | fbb.finish(payloadOffset); 33 | 34 | return fbb.asUint8Array(); 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/lib/Serializers/WifiScanCommand.ts: -------------------------------------------------------------------------------- 1 | import { Builder as FlatbufferBuilder } from 'flatbuffers'; 2 | import { WifiScanCommand } from '$lib/_fbs/open-shock/serialization/local/wifi-scan-command'; 3 | import { LocalToHubMessage } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message'; 4 | import { LocalToHubMessagePayload } from '$lib/_fbs/open-shock/serialization/local/local-to-hub-message-payload'; 5 | 6 | export function SerializeWifiScanCommand(scan: boolean): Uint8Array { 7 | const fbb = new FlatbufferBuilder(32); 8 | 9 | const cmdOffset = WifiScanCommand.createWifiScanCommand(fbb, scan); 10 | 11 | const payloadOffset = LocalToHubMessage.createLocalToHubMessage( 12 | fbb, 13 | LocalToHubMessagePayload.WifiScanCommand, 14 | cmdOffset 15 | ); 16 | 17 | fbb.finish(payloadOffset); 18 | 19 | return fbb.asUint8Array(); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/lib/TypeGuards/BasicGuards.ts: -------------------------------------------------------------------------------- 1 | export function isObject(data: unknown): data is object { 2 | return typeof data === 'object' && data !== null; 3 | } 4 | export function isString(value: unknown): value is string { 5 | return typeof value === 'string' || value instanceof String; 6 | } 7 | export function isNumber(value: unknown): value is number { 8 | return typeof value === 'number' && isFinite(value); 9 | } 10 | export function isArrayBuffer(value: unknown): value is ArrayBuffer { 11 | return value instanceof ArrayBuffer; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/configuration.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export { BackendConfig } from './configuration/backend-config'; 6 | export { CaptivePortalConfig } from './configuration/captive-portal-config'; 7 | export { EStopConfig } from './configuration/estop-config'; 8 | export { HubConfig } from './configuration/hub-config'; 9 | export { OtaUpdateChannel } from './configuration/ota-update-channel'; 10 | export { OtaUpdateConfig } from './configuration/ota-update-config'; 11 | export { OtaUpdateStep } from './configuration/ota-update-step'; 12 | export { RFConfig } from './configuration/rfconfig'; 13 | export { SerialInputConfig } from './configuration/serial-input-config'; 14 | export { WiFiConfig } from './configuration/wi-fi-config'; 15 | export { WiFiCredentials } from './configuration/wi-fi-credentials'; 16 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/configuration/ota-update-channel.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export enum OtaUpdateChannel { 6 | Stable = 0, 7 | Beta = 1, 8 | Develop = 2 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/configuration/ota-update-step.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export enum OtaUpdateStep { 6 | None = 0, 7 | Updating = 1, 8 | Updated = 2, 9 | Validating = 3, 10 | Validated = 4, 11 | RollingBack = 5 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/gateway.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export { BootStatus } from './gateway/boot-status'; 6 | export { HubToGatewayMessage } from './gateway/hub-to-gateway-message'; 7 | export { HubToGatewayMessagePayload } from './gateway/hub-to-gateway-message-payload'; 8 | export { OtaUpdateFailed } from './gateway/ota-update-failed'; 9 | export { OtaUpdateProgress } from './gateway/ota-update-progress'; 10 | export { OtaUpdateStarted } from './gateway/ota-update-started'; 11 | export { Pong } from './gateway/pong'; 12 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/gateway/ping.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | import * as flatbuffers from 'flatbuffers'; 6 | 7 | export class Ping { 8 | bb: flatbuffers.ByteBuffer|null = null; 9 | bb_pos = 0; 10 | __init(i:number, bb:flatbuffers.ByteBuffer):Ping { 11 | this.bb_pos = i; 12 | this.bb = bb; 13 | return this; 14 | } 15 | 16 | static getRootAsPing(bb:flatbuffers.ByteBuffer, obj?:Ping):Ping { 17 | return (obj || new Ping()).__init(bb.readInt32(bb.position()) + bb.position(), bb); 18 | } 19 | 20 | static getSizePrefixedRootAsPing(bb:flatbuffers.ByteBuffer, obj?:Ping):Ping { 21 | bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); 22 | return (obj || new Ping()).__init(bb.readInt32(bb.position()) + bb.position(), bb); 23 | } 24 | 25 | unixUtcTime():bigint { 26 | const offset = this.bb!.__offset(this.bb_pos, 4); 27 | return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0'); 28 | } 29 | 30 | static startPing(builder:flatbuffers.Builder) { 31 | builder.startObject(1); 32 | } 33 | 34 | static addUnixUtcTime(builder:flatbuffers.Builder, unixUtcTime:bigint) { 35 | builder.addFieldInt64(0, unixUtcTime, BigInt('0')); 36 | } 37 | 38 | static endPing(builder:flatbuffers.Builder):flatbuffers.Offset { 39 | const offset = builder.endObject(); 40 | return offset; 41 | } 42 | 43 | static createPing(builder:flatbuffers.Builder, unixUtcTime:bigint):flatbuffers.Offset { 44 | Ping.startPing(builder); 45 | Ping.addUnixUtcTime(builder, unixUtcTime); 46 | return Ping.endPing(builder); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/gateway/trigger-type.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export enum TriggerType { 6 | /** 7 | * Restart the hub 8 | */ 9 | Restart = 0, 10 | 11 | /** 12 | * Trigger the emergency stop on the hub, this does however not allow for resetting it 13 | */ 14 | EmergencyStop = 1, 15 | 16 | /** 17 | * Enable the captive portal 18 | */ 19 | CaptivePortalEnable = 2, 20 | 21 | /** 22 | * Disable the captive portal 23 | */ 24 | CaptivePortalDisable = 3 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/gateway/trigger.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | import * as flatbuffers from 'flatbuffers'; 6 | 7 | import { TriggerType } from '../../../open-shock/serialization/gateway/trigger-type'; 8 | 9 | 10 | export class Trigger { 11 | bb: flatbuffers.ByteBuffer|null = null; 12 | bb_pos = 0; 13 | __init(i:number, bb:flatbuffers.ByteBuffer):Trigger { 14 | this.bb_pos = i; 15 | this.bb = bb; 16 | return this; 17 | } 18 | 19 | static getRootAsTrigger(bb:flatbuffers.ByteBuffer, obj?:Trigger):Trigger { 20 | return (obj || new Trigger()).__init(bb.readInt32(bb.position()) + bb.position(), bb); 21 | } 22 | 23 | static getSizePrefixedRootAsTrigger(bb:flatbuffers.ByteBuffer, obj?:Trigger):Trigger { 24 | bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); 25 | return (obj || new Trigger()).__init(bb.readInt32(bb.position()) + bb.position(), bb); 26 | } 27 | 28 | type():TriggerType { 29 | const offset = this.bb!.__offset(this.bb_pos, 4); 30 | return offset ? this.bb!.readUint8(this.bb_pos + offset) : TriggerType.Restart; 31 | } 32 | 33 | static startTrigger(builder:flatbuffers.Builder) { 34 | builder.startObject(1); 35 | } 36 | 37 | static addType(builder:flatbuffers.Builder, type:TriggerType) { 38 | builder.addFieldInt8(0, type, TriggerType.Restart); 39 | } 40 | 41 | static endTrigger(builder:flatbuffers.Builder):flatbuffers.Offset { 42 | const offset = builder.endObject(); 43 | return offset; 44 | } 45 | 46 | static createTrigger(builder:flatbuffers.Builder, type:TriggerType):flatbuffers.Offset { 47 | Trigger.startTrigger(builder); 48 | Trigger.addType(builder, type); 49 | return Trigger.endTrigger(builder); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/local/account-link-result-code.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export enum AccountLinkResultCode { 6 | Success = 0, 7 | CodeRequired = 1, 8 | InvalidCodeLength = 2, 9 | NoInternetConnection = 3, 10 | InvalidCode = 4, 11 | RateLimited = 5, 12 | InternalError = 6 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/local/set-estop-pin-command.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | import * as flatbuffers from 'flatbuffers'; 6 | 7 | export class SetEstopPinCommand { 8 | bb: flatbuffers.ByteBuffer|null = null; 9 | bb_pos = 0; 10 | __init(i:number, bb:flatbuffers.ByteBuffer):SetEstopPinCommand { 11 | this.bb_pos = i; 12 | this.bb = bb; 13 | return this; 14 | } 15 | 16 | static getRootAsSetEstopPinCommand(bb:flatbuffers.ByteBuffer, obj?:SetEstopPinCommand):SetEstopPinCommand { 17 | return (obj || new SetEstopPinCommand()).__init(bb.readInt32(bb.position()) + bb.position(), bb); 18 | } 19 | 20 | static getSizePrefixedRootAsSetEstopPinCommand(bb:flatbuffers.ByteBuffer, obj?:SetEstopPinCommand):SetEstopPinCommand { 21 | bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); 22 | return (obj || new SetEstopPinCommand()).__init(bb.readInt32(bb.position()) + bb.position(), bb); 23 | } 24 | 25 | pin():number { 26 | const offset = this.bb!.__offset(this.bb_pos, 4); 27 | return offset ? this.bb!.readInt8(this.bb_pos + offset) : 0; 28 | } 29 | 30 | static startSetEstopPinCommand(builder:flatbuffers.Builder) { 31 | builder.startObject(1); 32 | } 33 | 34 | static addPin(builder:flatbuffers.Builder, pin:number) { 35 | builder.addFieldInt8(0, pin, 0); 36 | } 37 | 38 | static endSetEstopPinCommand(builder:flatbuffers.Builder):flatbuffers.Offset { 39 | const offset = builder.endObject(); 40 | return offset; 41 | } 42 | 43 | static createSetEstopPinCommand(builder:flatbuffers.Builder, pin:number):flatbuffers.Offset { 44 | SetEstopPinCommand.startSetEstopPinCommand(builder); 45 | SetEstopPinCommand.addPin(builder, pin); 46 | return SetEstopPinCommand.endSetEstopPinCommand(builder); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/local/set-gpioresult-code.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export enum SetGPIOResultCode { 6 | Success = 0, 7 | InvalidPin = 1, 8 | InternalError = 2 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/local/set-rf-tx-pin-command.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | import * as flatbuffers from 'flatbuffers'; 6 | 7 | export class SetRfTxPinCommand { 8 | bb: flatbuffers.ByteBuffer|null = null; 9 | bb_pos = 0; 10 | __init(i:number, bb:flatbuffers.ByteBuffer):SetRfTxPinCommand { 11 | this.bb_pos = i; 12 | this.bb = bb; 13 | return this; 14 | } 15 | 16 | static getRootAsSetRfTxPinCommand(bb:flatbuffers.ByteBuffer, obj?:SetRfTxPinCommand):SetRfTxPinCommand { 17 | return (obj || new SetRfTxPinCommand()).__init(bb.readInt32(bb.position()) + bb.position(), bb); 18 | } 19 | 20 | static getSizePrefixedRootAsSetRfTxPinCommand(bb:flatbuffers.ByteBuffer, obj?:SetRfTxPinCommand):SetRfTxPinCommand { 21 | bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); 22 | return (obj || new SetRfTxPinCommand()).__init(bb.readInt32(bb.position()) + bb.position(), bb); 23 | } 24 | 25 | pin():number { 26 | const offset = this.bb!.__offset(this.bb_pos, 4); 27 | return offset ? this.bb!.readInt8(this.bb_pos + offset) : 0; 28 | } 29 | 30 | static startSetRfTxPinCommand(builder:flatbuffers.Builder) { 31 | builder.startObject(1); 32 | } 33 | 34 | static addPin(builder:flatbuffers.Builder, pin:number) { 35 | builder.addFieldInt8(0, pin, 0); 36 | } 37 | 38 | static endSetRfTxPinCommand(builder:flatbuffers.Builder):flatbuffers.Offset { 39 | const offset = builder.endObject(); 40 | return offset; 41 | } 42 | 43 | static createSetRfTxPinCommand(builder:flatbuffers.Builder, pin:number):flatbuffers.Offset { 44 | SetRfTxPinCommand.startSetRfTxPinCommand(builder); 45 | SetRfTxPinCommand.addPin(builder, pin); 46 | return SetRfTxPinCommand.endSetRfTxPinCommand(builder); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/local/wifi-scan-command.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | import * as flatbuffers from 'flatbuffers'; 6 | 7 | export class WifiScanCommand { 8 | bb: flatbuffers.ByteBuffer|null = null; 9 | bb_pos = 0; 10 | __init(i:number, bb:flatbuffers.ByteBuffer):WifiScanCommand { 11 | this.bb_pos = i; 12 | this.bb = bb; 13 | return this; 14 | } 15 | 16 | static getRootAsWifiScanCommand(bb:flatbuffers.ByteBuffer, obj?:WifiScanCommand):WifiScanCommand { 17 | return (obj || new WifiScanCommand()).__init(bb.readInt32(bb.position()) + bb.position(), bb); 18 | } 19 | 20 | static getSizePrefixedRootAsWifiScanCommand(bb:flatbuffers.ByteBuffer, obj?:WifiScanCommand):WifiScanCommand { 21 | bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); 22 | return (obj || new WifiScanCommand()).__init(bb.readInt32(bb.position()) + bb.position(), bb); 23 | } 24 | 25 | run():boolean { 26 | const offset = this.bb!.__offset(this.bb_pos, 4); 27 | return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false; 28 | } 29 | 30 | static startWifiScanCommand(builder:flatbuffers.Builder) { 31 | builder.startObject(1); 32 | } 33 | 34 | static addRun(builder:flatbuffers.Builder, run:boolean) { 35 | builder.addFieldInt8(0, +run, +false); 36 | } 37 | 38 | static endWifiScanCommand(builder:flatbuffers.Builder):flatbuffers.Offset { 39 | const offset = builder.endObject(); 40 | return offset; 41 | } 42 | 43 | static createWifiScanCommand(builder:flatbuffers.Builder, run:boolean):flatbuffers.Offset { 44 | WifiScanCommand.startWifiScanCommand(builder); 45 | WifiScanCommand.addRun(builder, run); 46 | return WifiScanCommand.endWifiScanCommand(builder); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/types.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export { WifiScanStatus } from './types/wifi-scan-status'; 6 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/types/firmware-boot-type.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export enum FirmwareBootType { 6 | Normal = 0, 7 | NewFirmware = 1, 8 | Rollback = 2 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/types/ota-update-progress-task.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export enum OtaUpdateProgressTask { 6 | FetchingMetadata = 0, 7 | PreparingForUpdate = 1, 8 | FlashingFilesystem = 2, 9 | VerifyingFilesystem = 3, 10 | FlashingApplication = 4, 11 | MarkingApplicationBootable = 5, 12 | Rebooting = 6 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/types/shocker-command-type.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export enum ShockerCommandType { 6 | Stop = 0, 7 | Shock = 1, 8 | Vibrate = 2, 9 | Sound = 3 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/types/shocker-model-type.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export enum ShockerModelType { 6 | CaiXianlin = 0, 7 | Petrainer = 1, 8 | Petrainer998DR = 2 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/types/wifi-auth-mode.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export enum WifiAuthMode { 6 | Open = 0, 7 | WEP = 1, 8 | WPA_PSK = 2, 9 | WPA2_PSK = 3, 10 | WPA_WPA2_PSK = 4, 11 | WPA2_ENTERPRISE = 5, 12 | WPA3_PSK = 6, 13 | WPA2_WPA3_PSK = 7, 14 | WAPI_PSK = 8, 15 | UNKNOWN = 9 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/types/wifi-network-event-type.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export enum WifiNetworkEventType { 6 | Discovered = 0, 7 | Updated = 1, 8 | Lost = 2, 9 | Saved = 3, 10 | Removed = 4, 11 | Connected = 5, 12 | Disconnected = 6 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/lib/_fbs/open-shock/serialization/types/wifi-scan-status.ts: -------------------------------------------------------------------------------- 1 | // automatically generated by the FlatBuffers compiler, do not modify 2 | 3 | /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 4 | 5 | export enum WifiScanStatus { 6 | Started = 0, 7 | InProgress = 1, 8 | Completed = 2, 9 | TimedOut = 3, 10 | Aborted = 4, 11 | Error = 5 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/lib/components/Layout/Footer.svelte: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/Layout/Header.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
8 |
9 |
10 | 16 | OpenShock Logo 21 | 22 |
23 | 24 |
25 | 26 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/button/index.ts: -------------------------------------------------------------------------------- 1 | import Root, { 2 | type ButtonProps, 3 | type ButtonSize, 4 | type ButtonVariant, 5 | buttonVariants, 6 | } from "./button.svelte"; 7 | 8 | export { 9 | Root, 10 | type ButtonProps as Props, 11 | // 12 | Root as Button, 13 | buttonVariants, 14 | type ButtonProps, 15 | type ButtonSize, 16 | type ButtonVariant, 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-content.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 30 | {@render children?.()} 31 | 34 | 35 | Close 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-description.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-footer.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
19 | {@render children?.()} 20 |
21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-header.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
19 | {@render children?.()} 20 |
21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-overlay.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/dialog-title.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dialog/index.ts: -------------------------------------------------------------------------------- 1 | import { Dialog as DialogPrimitive } from "bits-ui"; 2 | 3 | import Title from "./dialog-title.svelte"; 4 | import Footer from "./dialog-footer.svelte"; 5 | import Header from "./dialog-header.svelte"; 6 | import Overlay from "./dialog-overlay.svelte"; 7 | import Content from "./dialog-content.svelte"; 8 | import Description from "./dialog-description.svelte"; 9 | 10 | const Root = DialogPrimitive.Root; 11 | const Trigger = DialogPrimitive.Trigger; 12 | const Close = DialogPrimitive.Close; 13 | const Portal = DialogPrimitive.Portal; 14 | 15 | export { 16 | Root, 17 | Title, 18 | Portal, 19 | Footer, 20 | Header, 21 | Trigger, 22 | Overlay, 23 | Content, 24 | Description, 25 | Close, 26 | // 27 | Root as Dialog, 28 | Title as DialogTitle, 29 | Portal as DialogPortal, 30 | Footer as DialogFooter, 31 | Header as DialogHeader, 32 | Trigger as DialogTrigger, 33 | Overlay as DialogOverlay, 34 | Content as DialogContent, 35 | Description as DialogDescription, 36 | Close as DialogClose, 37 | }; 38 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 30 | {#snippet children({ checked, indeterminate })} 31 | 32 | {#if indeterminate} 33 | 34 | {:else} 35 | 36 | {/if} 37 | 38 | {@render childrenProp?.()} 39 | {/snippet} 40 | 41 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
22 | {@render children?.()} 23 |
24 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 22 | {#snippet children({ checked })} 23 | 24 | {#if checked} 25 | 26 | {/if} 27 | 28 | {@render childrenProp?.({ checked })} 29 | {/snippet} 30 | 31 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | {@render children?.()} 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | {@render children?.()} 27 | 28 | 29 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/dropdown-menu/index.ts: -------------------------------------------------------------------------------- 1 | import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; 2 | import CheckboxItem from "./dropdown-menu-checkbox-item.svelte"; 3 | import Content from "./dropdown-menu-content.svelte"; 4 | import GroupHeading from "./dropdown-menu-group-heading.svelte"; 5 | import Item from "./dropdown-menu-item.svelte"; 6 | import Label from "./dropdown-menu-label.svelte"; 7 | import RadioItem from "./dropdown-menu-radio-item.svelte"; 8 | import Separator from "./dropdown-menu-separator.svelte"; 9 | import Shortcut from "./dropdown-menu-shortcut.svelte"; 10 | import SubContent from "./dropdown-menu-sub-content.svelte"; 11 | import SubTrigger from "./dropdown-menu-sub-trigger.svelte"; 12 | 13 | const Sub = DropdownMenuPrimitive.Sub; 14 | const Root = DropdownMenuPrimitive.Root; 15 | const Trigger = DropdownMenuPrimitive.Trigger; 16 | const Group = DropdownMenuPrimitive.Group; 17 | const RadioGroup = DropdownMenuPrimitive.RadioGroup; 18 | 19 | export { 20 | CheckboxItem, 21 | Content, 22 | Root as DropdownMenu, 23 | CheckboxItem as DropdownMenuCheckboxItem, 24 | Content as DropdownMenuContent, 25 | Group as DropdownMenuGroup, 26 | GroupHeading as DropdownMenuGroupHeading, 27 | Item as DropdownMenuItem, 28 | Label as DropdownMenuLabel, 29 | RadioGroup as DropdownMenuRadioGroup, 30 | RadioItem as DropdownMenuRadioItem, 31 | Separator as DropdownMenuSeparator, 32 | Shortcut as DropdownMenuShortcut, 33 | Sub as DropdownMenuSub, 34 | SubContent as DropdownMenuSubContent, 35 | SubTrigger as DropdownMenuSubTrigger, 36 | Trigger as DropdownMenuTrigger, 37 | Group, 38 | GroupHeading, 39 | Item, 40 | Label, 41 | RadioGroup, 42 | RadioItem, 43 | Root, 44 | Separator, 45 | Shortcut, 46 | Sub, 47 | SubContent, 48 | SubTrigger, 49 | Trigger, 50 | }; 51 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/input/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./input.svelte"; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Input, 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/input/input.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | {#if type === "file"} 24 | 35 | {:else} 36 | 46 | {/if} 47 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/label/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./label.svelte"; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Label, 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/label/label.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/scroll-area/index.ts: -------------------------------------------------------------------------------- 1 | import Scrollbar from "./scroll-area-scrollbar.svelte"; 2 | import Root from "./scroll-area.svelte"; 3 | 4 | export { 5 | Root, 6 | Scrollbar, 7 | //, 8 | Root as ScrollArea, 9 | Scrollbar as ScrollAreaScrollbar, 10 | }; 11 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | {@render children?.()} 26 | 29 | 30 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/scroll-area/scroll-area.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | {@render children?.()} 24 | 25 | {#if orientation === "vertical" || orientation === "both"} 26 | 27 | {/if} 28 | {#if orientation === "horizontal" || orientation === "both"} 29 | 30 | {/if} 31 | 32 | 33 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sonner/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Toaster } from "./sonner.svelte"; 2 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ui/sonner/sonner.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | -------------------------------------------------------------------------------- /frontend/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /frontend/src/lib/stores/UsedPinsStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | const { subscribe, update } = writable>(new Map()); 4 | 5 | export const UsedPinsStore = { 6 | subscribe, 7 | markPinUsed(pin: number, name: string) { 8 | update((store) => { 9 | // Remove any existing entries with the same pin or name 10 | for (const [key, value] of store) { 11 | if (key === pin || value === name) { 12 | store.delete(key); 13 | } 14 | } 15 | 16 | store.set(pin, name); 17 | 18 | return store; 19 | }); 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /frontend/src/lib/stores/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ColorSchemeStore'; 2 | export * from './HubStateStore'; 3 | export * from './UsedPinsStore'; 4 | -------------------------------------------------------------------------------- /frontend/src/lib/types/HubState.ts: -------------------------------------------------------------------------------- 1 | import type { WifiScanStatus } from '$lib/_fbs/open-shock/serialization/types/wifi-scan-status'; 2 | import type { Config } from '$lib/mappers/ConfigMapper'; 3 | import type { WiFiNetwork, WiFiNetworkGroup } from './'; 4 | 5 | export type HubState = { 6 | wifiConnectedBSSID: string | null; 7 | wifiScanStatus: WifiScanStatus | null; 8 | wifiNetworks: Map; 9 | wifiNetworkGroups: Map; 10 | accountLinked: boolean; 11 | config: Config | null; 12 | gpioValidInputs: Int8Array; 13 | gpioValidOutputs: Int8Array; 14 | }; 15 | -------------------------------------------------------------------------------- /frontend/src/lib/types/WiFiNetwork.ts: -------------------------------------------------------------------------------- 1 | import type { WifiAuthMode } from '$lib/_fbs/open-shock/serialization/types/wifi-auth-mode'; 2 | 3 | export type WiFiNetwork = { 4 | ssid: string; 5 | bssid: string; 6 | rssi: number; 7 | channel: number; 8 | security: WifiAuthMode; 9 | saved: boolean; 10 | }; 11 | -------------------------------------------------------------------------------- /frontend/src/lib/types/WiFiNetworkGroup.ts: -------------------------------------------------------------------------------- 1 | import type { WifiAuthMode } from '$lib/_fbs/open-shock/serialization/types/wifi-auth-mode'; 2 | 3 | export type WiFiNetworkGroup = { 4 | ssid: string; 5 | saved: boolean; 6 | security: WifiAuthMode; 7 | networks: { 8 | bssid: string; 9 | rssi: number; 10 | channel: number; 11 | }[]; 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/src/lib/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './HubState'; 2 | export * from './WiFiNetwork'; 3 | export * from './WiFiNetworkGroup'; 4 | -------------------------------------------------------------------------------- /frontend/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 24 |
25 |
26 | {@render children?.()} 27 |
28 |
29 | -------------------------------------------------------------------------------- /frontend/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | export const prerender = true; 2 | -------------------------------------------------------------------------------- /frontend/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenShock/Firmware/70dd3619e14ff939025a137e5db1defdd00131bc/frontend/static/favicon.ico -------------------------------------------------------------------------------- /frontend/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://svelte.dev/docs/kit/integrations 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. 12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 13 | // See https://svelte.dev/docs/kit/adapters for more information about adapters. 14 | adapter: adapter(), 15 | output: { 16 | bundleStrategy: 'inline', 17 | }, 18 | }, 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 15 | // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type UserConfig } from 'vite'; 2 | import { sveltekit } from '@sveltejs/kit/vite'; 3 | import tailwindcss from '@tailwindcss/vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [sveltekit(), tailwindcss()], 7 | 8 | test: { 9 | include: ['src/**/*.{test,spec}.{js,ts}'], 10 | }, 11 | } as UserConfig); // TODO: "test" is not a valid property of the defineconfig argument? This needs to get fixed 12 | -------------------------------------------------------------------------------- /include/AccountLinkResultCode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serialization/_fbs/HubToLocalMessage_generated.h" 4 | 5 | #include 6 | 7 | namespace OpenShock { 8 | typedef OpenShock::Serialization::Local::AccountLinkResultCode AccountLinkResultCode; 9 | } // namespace OpenShock 10 | -------------------------------------------------------------------------------- /include/CaptivePortal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace OpenShock::CaptivePortal { 7 | [[nodiscard]] bool Init(); 8 | 9 | void SetAlwaysEnabled(bool alwaysEnabled); 10 | bool IsAlwaysEnabled(); 11 | 12 | bool ForceClose(uint32_t timeoutMs); 13 | 14 | bool IsRunning(); 15 | 16 | bool SendMessageTXT(uint8_t socketId, std::string_view data); 17 | bool SendMessageBIN(uint8_t socketId, const uint8_t* data, std::size_t len); 18 | 19 | bool BroadcastMessageTXT(std::string_view data); 20 | bool BroadcastMessageBIN(const uint8_t* data, std::size_t len); 21 | } // namespace OpenShock::CaptivePortal 22 | -------------------------------------------------------------------------------- /include/CaptivePortalInstance.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | #include "WebSocketDeFragger.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | namespace OpenShock { 17 | class CaptivePortalInstance { 18 | DISABLE_COPY(CaptivePortalInstance); 19 | DISABLE_MOVE(CaptivePortalInstance); 20 | 21 | public: 22 | CaptivePortalInstance(); 23 | ~CaptivePortalInstance(); 24 | 25 | bool sendMessageTXT(uint8_t socketId, std::string_view data) { return m_socketServer.sendTXT(socketId, data.data(), data.length()); } 26 | bool sendMessageBIN(uint8_t socketId, const uint8_t* data, std::size_t len) { return m_socketServer.sendBIN(socketId, data, len); } 27 | bool broadcastMessageTXT(std::string_view data) { return m_socketServer.broadcastTXT(data.data(), data.length()); } 28 | bool broadcastMessageBIN(const uint8_t* data, std::size_t len) { return m_socketServer.broadcastBIN(data, len); } 29 | 30 | private: 31 | void task(); 32 | void handleWebSocketClientConnected(uint8_t socketId); 33 | void handleWebSocketClientDisconnected(uint8_t socketId); 34 | void handleWebSocketClientError(uint8_t socketId, uint16_t code, const char* message); 35 | void handleWebSocketEvent(uint8_t socketId, WebSocketMessageType type, const uint8_t* payload, std::size_t length); 36 | 37 | AsyncWebServer m_webServer; 38 | WebSocketsServer m_socketServer; 39 | WebSocketDeFragger m_socketDeFragger; 40 | fs::LittleFSFS m_fileSystem; 41 | DNSServer m_dnsServer; 42 | TaskHandle_t m_taskHandle; 43 | }; 44 | } // namespace OpenShock 45 | -------------------------------------------------------------------------------- /include/CommandHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SetGPIOResultCode.h" 4 | #include "ShockerCommandType.h" 5 | #include "ShockerModelType.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | // TODO: This is horrible architecture. Fix it. 12 | 13 | namespace OpenShock::CommandHandler { 14 | [[nodiscard]] bool Init(); 15 | bool Ok(); 16 | 17 | gpio_num_t GetRfTxPin(); 18 | SetGPIOResultCode SetRfTxPin(gpio_num_t txPin); 19 | 20 | bool SetKeepAliveEnabled(bool enabled); 21 | 22 | bool HandleCommand(ShockerModelType shockerModel, uint16_t shockerId, ShockerCommandType type, uint8_t intensity, uint16_t durationMs); 23 | } // namespace OpenShock::CommandHandler 24 | -------------------------------------------------------------------------------- /include/Convert.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace OpenShock::Convert { // TODO: C++23 make this use std::from_chars instead 10 | void FromInt8(int8_t val, std::string& str); 11 | void FromUint8(uint8_t val, std::string& str); 12 | void FromInt16(int16_t val, std::string& str); 13 | void FromUint16(uint16_t val, std::string& str); 14 | void FromInt32(int32_t val, std::string& str); 15 | void FromUint32(uint32_t val, std::string& str); 16 | void FromInt64(int64_t val, std::string& str); 17 | void FromUint64(uint64_t val, std::string& str); 18 | void FromSizeT(size_t val, std::string& str); 19 | void FromBool(bool val, std::string& str); 20 | void FromGpioNum(gpio_num_t val, std::string& str); 21 | 22 | bool ToInt8(std::string_view str, int8_t& val); 23 | bool ToUint8(std::string_view str, uint8_t& val); 24 | bool ToInt16(std::string_view str, int16_t& val); 25 | bool ToUint16(std::string_view str, uint16_t& val); 26 | bool ToInt32(std::string_view str, int32_t& val); 27 | bool ToUint32(std::string_view str, uint32_t& val); 28 | bool ToInt64(std::string_view str, int64_t& val); 29 | bool ToUint64(std::string_view str, uint64_t& val); 30 | bool ToSizeT(std::string_view str, size_t& val); 31 | bool ToBool(std::string_view str, bool& val); 32 | bool ToGpioNum(std::string_view str, gpio_num_t& val); 33 | } // namespace OpenShock::Convert 34 | -------------------------------------------------------------------------------- /include/Core.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace OpenShock { 8 | /** 9 | * @brief Returns the current time in microseconds 10 | * 11 | * @return int64_t The current time in microseconds 12 | * 13 | * @note This function overflows after 292471 years 14 | */ 15 | inline int64_t micros() { 16 | return esp_timer_get_time(); 17 | } 18 | 19 | /** 20 | * @brief Returns the current time in milliseconds 21 | * 22 | * @return int64_t The current time in milliseconds 23 | * 24 | * @note This function overflows after 292471208 years 25 | */ 26 | inline int64_t millis() { 27 | return esp_timer_get_time() / 1000LL; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /include/FirmwareBootType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serialization/_fbs/FirmwareBootType_generated.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace OpenShock { 9 | typedef OpenShock::Serialization::Types::FirmwareBootType FirmwareBootType; 10 | 11 | inline bool TryParseFirmwareBootType(FirmwareBootType& bootType, const char* str) { 12 | if (strcasecmp(str, "normal") == 0) { 13 | bootType = FirmwareBootType::Normal; 14 | return true; 15 | } 16 | 17 | if (strcasecmp(str, "newfirmware") == 0 || strcasecmp(str, "new_firmware") == 0) { 18 | bootType = FirmwareBootType::NewFirmware; 19 | return true; 20 | } 21 | 22 | if (strcasecmp(str, "rollback") == 0) { 23 | bootType = FirmwareBootType::Rollback; 24 | return true; 25 | } 26 | 27 | return false; 28 | } 29 | } // namespace OpenShock 30 | -------------------------------------------------------------------------------- /include/FormatHelpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define BSSID_FMT "%02X:%02X:%02X:%02X:%02X:%02X" 4 | #define BSSID_ARG(bssid) bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5] 5 | #define BSSID_FMT_LEN 18 6 | 7 | #define IPV4ADDR_FMT "%hhu.%hhu.%hhu.%hhu" 8 | #define IPV4ADDR_ARG(addr) addr[0], addr[1], addr[2], addr[3] 9 | #define IPV4ADDR_FMT_LEN 15 10 | 11 | #define IPV6ADDR_FMT "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x" 12 | #define IPV6ADDR_ARG(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15] 13 | #define IPV6ADDR_FMT_LEN 39 14 | -------------------------------------------------------------------------------- /include/GatewayClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | #include "GatewayClientState.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace OpenShock { 14 | class GatewayClient { 15 | DISABLE_COPY(GatewayClient); 16 | DISABLE_MOVE(GatewayClient); 17 | 18 | public: 19 | GatewayClient(const std::string& authToken); 20 | ~GatewayClient(); 21 | 22 | inline GatewayClientState state() const { return m_state; } 23 | 24 | void connect(const std::string& host, uint16_t port, const std::string& path); 25 | void disconnect(); 26 | 27 | bool sendMessageTXT(std::string_view data); 28 | bool sendMessageBIN(const uint8_t* data, std::size_t length); 29 | 30 | bool loop(); 31 | 32 | private: 33 | void _setState(GatewayClientState state); 34 | void _sendBootStatus(); 35 | void _handleEvent(WStype_t type, uint8_t* payload, std::size_t length); 36 | 37 | WebSocketsClient m_webSocket; 38 | GatewayClientState m_state; 39 | }; 40 | } // namespace OpenShock 41 | -------------------------------------------------------------------------------- /include/GatewayClientState.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace OpenShock { 6 | enum class GatewayClientState : uint8_t { 7 | Disconnected, 8 | Disconnecting, 9 | Connecting, 10 | Connected, 11 | }; 12 | } // namespace OpenShock 13 | -------------------------------------------------------------------------------- /include/GatewayConnectionManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AccountLinkResultCode.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace OpenShock::GatewayConnectionManager { 10 | [[nodiscard]] bool Init(); 11 | 12 | bool IsConnected(); 13 | 14 | bool IsLinked(); 15 | AccountLinkResultCode Link(std::string_view linkCode); 16 | void UnLink(); 17 | 18 | bool SendMessageTXT(std::string_view data); 19 | bool SendMessageBIN(const uint8_t* data, std::size_t length); 20 | 21 | void Update(); 22 | } // namespace OpenShock::GatewayConnectionManager 23 | -------------------------------------------------------------------------------- /include/OtaUpdateChannel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serialization/_fbs/HubConfig_generated.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace OpenShock { 9 | typedef OpenShock::Serialization::Configuration::OtaUpdateChannel OtaUpdateChannel; 10 | 11 | inline bool TryParseOtaUpdateChannel(OtaUpdateChannel& channel, const char* str) { 12 | if (strcasecmp(str, "stable") == 0) { 13 | channel = OtaUpdateChannel::Stable; 14 | return true; 15 | } 16 | 17 | if (strcasecmp(str, "beta") == 0) { 18 | channel = OtaUpdateChannel::Beta; 19 | return true; 20 | } 21 | 22 | if (strcasecmp(str, "develop") == 0 || strcasecmp(str, "dev") == 0) { 23 | channel = OtaUpdateChannel::Develop; 24 | return true; 25 | } 26 | 27 | return false; 28 | } 29 | } // namespace OpenShock 30 | -------------------------------------------------------------------------------- /include/OtaUpdateManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FirmwareBootType.h" 4 | #include "OtaUpdateChannel.h" 5 | #include "SemVer.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace OpenShock::OtaUpdateManager { 13 | [[nodiscard]] bool Init(); 14 | 15 | struct FirmwareRelease { 16 | std::string appBinaryUrl; 17 | uint8_t appBinaryHash[32]; 18 | std::string filesystemBinaryUrl; 19 | uint8_t filesystemBinaryHash[32]; 20 | }; 21 | 22 | bool TryGetFirmwareVersion(OtaUpdateChannel channel, OpenShock::SemVer& version); 23 | bool TryGetFirmwareBoards(const OpenShock::SemVer& version, std::vector& boards); 24 | bool TryGetFirmwareRelease(const OpenShock::SemVer& version, FirmwareRelease& release); 25 | 26 | bool TryStartFirmwareUpdate(const OpenShock::SemVer& version); 27 | 28 | FirmwareBootType GetFirmwareBootType(); 29 | bool IsValidatingApp(); 30 | 31 | void InvalidateAndRollback(); 32 | void ValidateApp(); 33 | } // namespace OpenShock::OtaUpdateManager 34 | -------------------------------------------------------------------------------- /include/OtaUpdateStep.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serialization/_fbs/HubConfig_generated.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace OpenShock { 9 | typedef OpenShock::Serialization::Configuration::OtaUpdateStep OtaUpdateStep; 10 | 11 | inline bool TryParseOtaUpdateStep(OtaUpdateStep& channel, const char* str) { 12 | if (strcasecmp(str, "none") == 0) { 13 | channel = OtaUpdateStep::None; 14 | return true; 15 | } 16 | 17 | if (strcasecmp(str, "updating") == 0) { 18 | channel = OtaUpdateStep::Updating; 19 | return true; 20 | } 21 | 22 | if (strcasecmp(str, "updated") == 0) { 23 | channel = OtaUpdateStep::Updated; 24 | return true; 25 | } 26 | 27 | if (strcasecmp(str, "validating") == 0) { 28 | channel = OtaUpdateStep::Validating; 29 | return true; 30 | } 31 | 32 | if (strcasecmp(str, "validated") == 0) { 33 | channel = OtaUpdateStep::Validated; 34 | return true; 35 | } 36 | 37 | if (strcasecmp(str, "rollingback") == 0) { 38 | channel = OtaUpdateStep::RollingBack; 39 | return true; 40 | } 41 | 42 | return false; 43 | } 44 | } // namespace OpenShock 45 | -------------------------------------------------------------------------------- /include/PinPatternManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | #include "SimpleMutex.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace OpenShock { 14 | class PinPatternManager { 15 | DISABLE_COPY(PinPatternManager); 16 | DISABLE_MOVE(PinPatternManager); 17 | 18 | public: 19 | struct State { 20 | bool level; 21 | uint32_t duration; 22 | }; 23 | 24 | PinPatternManager() = delete; 25 | PinPatternManager(gpio_num_t gpioPin); 26 | ~PinPatternManager(); 27 | 28 | bool IsValid() const { return m_gpioPin != GPIO_NUM_NC; } 29 | 30 | void SetPattern(const State* pattern, std::size_t patternLength); 31 | template 32 | inline void SetPattern(const State (&pattern)[N]) 33 | { 34 | SetPattern(pattern, N); 35 | } 36 | void ClearPattern(); 37 | 38 | private: 39 | void ClearPatternInternal(); 40 | void RunPattern(); 41 | 42 | gpio_num_t m_gpioPin; 43 | std::vector m_pattern; 44 | TaskHandle_t m_taskHandle; 45 | SimpleMutex m_taskMutex; 46 | }; 47 | } // namespace OpenShock 48 | -------------------------------------------------------------------------------- /include/RGBPatternManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | #include "SimpleMutex.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace OpenShock { 16 | class RGBPatternManager { 17 | DISABLE_COPY(RGBPatternManager); 18 | DISABLE_MOVE(RGBPatternManager); 19 | 20 | public: 21 | RGBPatternManager() = delete; 22 | RGBPatternManager(gpio_num_t gpioPin); 23 | ~RGBPatternManager(); 24 | 25 | bool IsValid() const { return m_gpioPin != GPIO_NUM_NC; } 26 | 27 | struct RGBState { 28 | uint8_t red; 29 | uint8_t green; 30 | uint8_t blue; 31 | uint32_t duration; 32 | }; 33 | 34 | void SetPattern(const RGBState* pattern, std::size_t patternLength); 35 | template 36 | inline void SetPattern(const RGBState (&pattern)[N]) 37 | { 38 | SetPattern(pattern, N); 39 | } 40 | void SetBrightness(uint8_t brightness); 41 | void ClearPattern(); 42 | 43 | private: 44 | void ClearPatternInternal(); 45 | static void RunPattern(void* arg); 46 | 47 | gpio_num_t m_gpioPin; 48 | uint8_t m_brightness; // 0-255 49 | std::vector m_pattern; 50 | rmt_obj_t* m_rmtHandle; 51 | TaskHandle_t m_taskHandle; 52 | SimpleMutex m_taskMutex; 53 | }; 54 | } // namespace OpenShock 55 | -------------------------------------------------------------------------------- /include/RateLimiter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | #include "SimpleMutex.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace OpenShock { 10 | class RateLimiter { 11 | DISABLE_COPY(RateLimiter); 12 | DISABLE_MOVE(RateLimiter); 13 | 14 | public: 15 | RateLimiter(); 16 | ~RateLimiter(); 17 | 18 | void addLimit(uint32_t durationMs, uint16_t count); 19 | void clearLimits(); 20 | 21 | bool tryRequest(); 22 | void clearRequests(); 23 | 24 | void blockFor(int64_t blockForMs); 25 | 26 | private: 27 | struct Limit { 28 | int64_t durationMs; 29 | uint16_t count; 30 | }; 31 | 32 | OpenShock::SimpleMutex m_mutex; 33 | int64_t m_nextSlot; 34 | int64_t m_nextCleanup; 35 | std::vector m_limits; 36 | std::vector m_requests; 37 | }; 38 | } // namespace OpenShock 39 | -------------------------------------------------------------------------------- /include/SetGPIOResultCode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serialization/_fbs/HubToLocalMessage_generated.h" 4 | 5 | #include 6 | 7 | namespace OpenShock { 8 | typedef OpenShock::Serialization::Local::SetGPIOResultCode SetGPIOResultCode; 9 | } // namespace OpenShock 10 | -------------------------------------------------------------------------------- /include/ShockerCommandType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace OpenShock { 7 | enum class ShockerCommandType : uint8_t { 8 | Stop, 9 | Shock, 10 | Vibrate, 11 | Sound, 12 | Light 13 | }; 14 | 15 | inline bool ShockerCommandTypeFromString(const char* str, ShockerCommandType& out) { 16 | if (strcasecmp(str, "stop") == 0) { 17 | out = ShockerCommandType::Stop; 18 | return true; 19 | } else if (strcasecmp(str, "shock") == 0) { 20 | out = ShockerCommandType::Shock; 21 | return true; 22 | } else if (strcasecmp(str, "vibrate") == 0) { 23 | out = ShockerCommandType::Vibrate; 24 | return true; 25 | } else if (strcasecmp(str, "sound") == 0) { 26 | out = ShockerCommandType::Sound; 27 | return true; 28 | } else if (strcasecmp(str, "light") == 0) { 29 | out = ShockerCommandType::Light; 30 | return true; 31 | } else { 32 | return false; 33 | } 34 | } 35 | } // namespace OpenShock 36 | -------------------------------------------------------------------------------- /include/ShockerModelType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serialization/_fbs/ShockerModelType_generated.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace OpenShock { 9 | typedef OpenShock::Serialization::Types::ShockerModelType ShockerModelType; 10 | 11 | inline bool ShockerModelTypeFromString(const char* str, ShockerModelType& out, bool allowTypo = false) { 12 | if (strcasecmp(str, "caixianlin") == 0 || strcasecmp(str, "cai-xianlin") == 0) { 13 | out = ShockerModelType::CaiXianlin; 14 | return true; 15 | } 16 | 17 | if (strcasecmp(str, "petrainer") == 0) { 18 | out = ShockerModelType::Petrainer; 19 | return true; 20 | } 21 | 22 | if (allowTypo && strcasecmp(str, "pettrainer") == 0) { 23 | out = ShockerModelType::Petrainer; 24 | return true; 25 | } 26 | 27 | if (strcasecmp(str, "petrainer998dr") == 0) { 28 | out = ShockerModelType::Petrainer998DR; 29 | return true; 30 | } 31 | 32 | if (allowTypo && strcasecmp(str, "pettrainer998dr") == 0) { 33 | out = ShockerModelType::Petrainer998DR; 34 | return true; 35 | } 36 | 37 | return false; 38 | } 39 | } // namespace OpenShock 40 | -------------------------------------------------------------------------------- /include/SimpleMutex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Common.h" 6 | 7 | namespace OpenShock { 8 | class SimpleMutex { 9 | DISABLE_COPY(SimpleMutex); 10 | DISABLE_MOVE(SimpleMutex); 11 | 12 | public: 13 | SimpleMutex(); 14 | ~SimpleMutex(); 15 | 16 | bool lock(TickType_t xTicksToWait); 17 | void unlock(); 18 | 19 | private: 20 | SemaphoreHandle_t m_mutex; 21 | }; 22 | 23 | class ScopedLock { 24 | DISABLE_COPY(ScopedLock); 25 | DISABLE_MOVE(ScopedLock); 26 | 27 | public: 28 | ScopedLock(SimpleMutex* mutex, TickType_t xTicksToWait = portMAX_DELAY) 29 | : m_mutex(mutex) 30 | { 31 | bool result = false; 32 | if (m_mutex != nullptr) { 33 | result = m_mutex->lock(xTicksToWait); 34 | } 35 | 36 | if (!result) { 37 | m_mutex = nullptr; 38 | } 39 | } 40 | 41 | ~ScopedLock() 42 | { 43 | if (m_mutex != nullptr) { 44 | m_mutex->unlock(); 45 | } 46 | } 47 | 48 | bool isLocked() const { return m_mutex != nullptr; } 49 | 50 | bool unlock() 51 | { 52 | if (m_mutex != nullptr) { 53 | m_mutex->unlock(); 54 | m_mutex = nullptr; 55 | return true; 56 | } 57 | 58 | return false; 59 | } 60 | 61 | SimpleMutex* getMutex() const { return m_mutex; } 62 | 63 | private: 64 | SimpleMutex* m_mutex; 65 | }; 66 | } // namespace OpenShock 67 | -------------------------------------------------------------------------------- /include/VisualStateManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "estop/EStopManager.h" 4 | #include 5 | 6 | namespace OpenShock::VisualStateManager { 7 | [[nodiscard]] bool Init(); 8 | 9 | void SetCriticalError(); 10 | void SetScanningStarted(); 11 | } // namespace OpenShock::VisualStateManager 12 | -------------------------------------------------------------------------------- /include/WebSocketDeFragger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | #include "WebSocketMessageType.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace OpenShock { 13 | class WebSocketDeFragger { 14 | DISABLE_COPY(WebSocketDeFragger); 15 | DISABLE_MOVE(WebSocketDeFragger); 16 | 17 | public: 18 | typedef std::function EventCallback; 19 | 20 | WebSocketDeFragger(EventCallback callback); 21 | ~WebSocketDeFragger(); 22 | 23 | void handler(uint8_t socketId, WStype_t type, const uint8_t* payload, std::size_t length); 24 | void onEvent(const EventCallback& callback); 25 | void clear(uint8_t socketId); 26 | void clear(); 27 | 28 | private: 29 | void start(uint8_t socketId, WebSocketMessageType type, const uint8_t* data, uint32_t length); 30 | void append(uint8_t socketId, const uint8_t* data, uint32_t length); 31 | void finish(uint8_t socketId, const uint8_t* data, uint32_t length); 32 | 33 | struct Message { 34 | uint8_t* data; 35 | uint32_t size; 36 | uint32_t capacity; 37 | WebSocketMessageType type; 38 | }; 39 | 40 | std::map m_messages; 41 | EventCallback m_callback; 42 | }; 43 | } // namespace OpenShock 44 | -------------------------------------------------------------------------------- /include/WebSocketMessageType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace OpenShock { 6 | enum class WebSocketMessageType : uint8_t { 7 | Error, 8 | Disconnected, 9 | Connected, 10 | Text, 11 | Binary, 12 | Ping, 13 | Pong 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /include/config/BackendConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config/ConfigBase.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace OpenShock::Config { 9 | struct BackendConfig : public ConfigBase { 10 | BackendConfig(); 11 | BackendConfig(std::string_view domain, std::string_view authToken); 12 | 13 | std::string domain; 14 | std::string authToken; 15 | 16 | void ToDefault() override; 17 | 18 | bool FromFlatbuffers(const Serialization::Configuration::BackendConfig* config) override; 19 | [[nodiscard]] flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const override; 20 | 21 | bool FromJSON(const cJSON* json) override; 22 | [[nodiscard]] cJSON* ToJSON(bool withSensitiveData) const override; 23 | }; 24 | } // namespace OpenShock::Config 25 | -------------------------------------------------------------------------------- /include/config/CaptivePortalConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config/ConfigBase.h" 4 | 5 | namespace OpenShock::Config { 6 | struct CaptivePortalConfig : public ConfigBase { 7 | CaptivePortalConfig(); 8 | CaptivePortalConfig(bool alwaysEnabled); 9 | 10 | bool alwaysEnabled; 11 | 12 | void ToDefault() override; 13 | 14 | bool FromFlatbuffers(const Serialization::Configuration::CaptivePortalConfig* config) override; 15 | [[nodiscard]] flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const override; 16 | 17 | bool FromJSON(const cJSON* json) override; 18 | [[nodiscard]] cJSON* ToJSON(bool withSensitiveData) const override; 19 | }; 20 | } // namespace OpenShock::Config 21 | -------------------------------------------------------------------------------- /include/config/ConfigBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serialization/_fbs/HubConfig_generated.h" 4 | 5 | #include 6 | 7 | namespace OpenShock::Config { 8 | template 9 | struct ConfigBase { 10 | virtual void ToDefault() = 0; 11 | 12 | virtual bool FromFlatbuffers(const T* config) = 0; 13 | [[nodiscard]] virtual flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const = 0; 14 | 15 | virtual bool FromJSON(const cJSON* json) = 0; 16 | [[nodiscard]] virtual cJSON* ToJSON(bool withSensitiveData) const = 0; 17 | }; 18 | 19 | } // namespace OpenShock::Config 20 | -------------------------------------------------------------------------------- /include/config/EStopConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config/ConfigBase.h" 4 | 5 | #include 6 | 7 | namespace OpenShock::Config { 8 | struct EStopConfig : public ConfigBase { 9 | EStopConfig(); 10 | EStopConfig(bool enabled, gpio_num_t gpioPin); 11 | 12 | bool enabled; 13 | gpio_num_t gpioPin; 14 | 15 | void ToDefault() override; 16 | 17 | bool FromFlatbuffers(const Serialization::Configuration::EStopConfig* config) override; 18 | flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const override; 19 | 20 | bool FromJSON(const cJSON* json) override; 21 | cJSON* ToJSON(bool withSensitiveData) const override; 22 | }; 23 | } // namespace OpenShock::Config 24 | -------------------------------------------------------------------------------- /include/config/OtaUpdateConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config/ConfigBase.h" 4 | #include "FirmwareBootType.h" 5 | #include "OtaUpdateChannel.h" 6 | #include "OtaUpdateStep.h" 7 | 8 | #include 9 | 10 | namespace OpenShock::Config { 11 | struct OtaUpdateConfig : public ConfigBase { 12 | OtaUpdateConfig(); 13 | OtaUpdateConfig( 14 | bool isEnabled, 15 | std::string cdnDomain, 16 | OtaUpdateChannel updateChannel, 17 | bool checkOnStartup, 18 | bool checkPeriodically, 19 | uint16_t checkInterval, 20 | bool allowBackendManagement, 21 | bool requireManualApproval, 22 | int32_t updateId, 23 | OtaUpdateStep updateStep 24 | ); 25 | 26 | bool isEnabled; 27 | std::string cdnDomain; 28 | OtaUpdateChannel updateChannel; 29 | bool checkOnStartup; 30 | bool checkPeriodically; 31 | uint16_t checkInterval; 32 | bool allowBackendManagement; 33 | bool requireManualApproval; 34 | int32_t updateId; 35 | OtaUpdateStep updateStep; 36 | 37 | void ToDefault() override; 38 | 39 | bool FromFlatbuffers(const Serialization::Configuration::OtaUpdateConfig* config) override; 40 | [[nodiscard]] flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const override; 41 | 42 | bool FromJSON(const cJSON* json) override; 43 | [[nodiscard]] cJSON* ToJSON(bool withSensitiveData) const override; 44 | }; 45 | } // namespace OpenShock::Config 46 | -------------------------------------------------------------------------------- /include/config/RFConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "config/ConfigBase.h" 6 | 7 | namespace OpenShock::Config { 8 | struct RFConfig : public ConfigBase { 9 | RFConfig(); 10 | RFConfig(gpio_num_t txPin, bool keepAliveEnabled); 11 | 12 | gpio_num_t txPin; 13 | bool keepAliveEnabled; 14 | 15 | void ToDefault() override; 16 | 17 | bool FromFlatbuffers(const Serialization::Configuration::RFConfig* config) override; 18 | [[nodiscard]] flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const override; 19 | 20 | bool FromJSON(const cJSON* json) override; 21 | [[nodiscard]] cJSON* ToJSON(bool withSensitiveData) const override; 22 | }; 23 | } // namespace OpenShock::Config 24 | -------------------------------------------------------------------------------- /include/config/RootConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config/BackendConfig.h" 4 | #include "config/CaptivePortalConfig.h" 5 | #include "config/ConfigBase.h" 6 | #include "config/EStopConfig.h" 7 | #include "config/OtaUpdateConfig.h" 8 | #include "config/RFConfig.h" 9 | #include "config/SerialInputConfig.h" 10 | #include "config/WiFiConfig.h" 11 | 12 | namespace OpenShock::Config { 13 | struct RootConfig : public ConfigBase { 14 | RootConfig(); 15 | 16 | OpenShock::Config::RFConfig rf; 17 | OpenShock::Config::WiFiConfig wifi; 18 | OpenShock::Config::CaptivePortalConfig captivePortal; 19 | OpenShock::Config::BackendConfig backend; 20 | OpenShock::Config::SerialInputConfig serialInput; 21 | OpenShock::Config::OtaUpdateConfig otaUpdate; 22 | OpenShock::Config::EStopConfig estop; 23 | 24 | void ToDefault() override; 25 | 26 | bool FromFlatbuffers(const Serialization::Configuration::HubConfig* config) override; 27 | [[nodiscard]] flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const override; 28 | 29 | bool FromJSON(const cJSON* json) override; 30 | [[nodiscard]] cJSON* ToJSON(bool withSensitiveData) const override; 31 | }; 32 | } // namespace OpenShock::Config 33 | -------------------------------------------------------------------------------- /include/config/SerialInputConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config/ConfigBase.h" 4 | 5 | namespace OpenShock::Config { 6 | struct SerialInputConfig : public ConfigBase { 7 | SerialInputConfig(); 8 | SerialInputConfig(bool echoEnabled); 9 | 10 | bool echoEnabled; 11 | 12 | void ToDefault() override; 13 | 14 | bool FromFlatbuffers(const Serialization::Configuration::SerialInputConfig* config) override; 15 | [[nodiscard]] flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const override; 16 | 17 | bool FromJSON(const cJSON* json) override; 18 | [[nodiscard]] cJSON* ToJSON(bool withSensitiveData) const override; 19 | }; 20 | } // namespace OpenShock::Config 21 | -------------------------------------------------------------------------------- /include/config/WiFiConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config/ConfigBase.h" 4 | #include "config/WiFiCredentials.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace OpenShock::Config { 11 | struct WiFiConfig : public ConfigBase { 12 | WiFiConfig(); 13 | WiFiConfig(std::string_view accessPointSSID, std::string_view hostname, const std::vector& credentialsList); 14 | 15 | std::string accessPointSSID; 16 | std::string hostname; 17 | std::vector credentialsList; 18 | 19 | void ToDefault() override; 20 | 21 | bool FromFlatbuffers(const Serialization::Configuration::WiFiConfig* config) override; 22 | [[nodiscard]] flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const override; 23 | 24 | bool FromJSON(const cJSON* json) override; 25 | [[nodiscard]] cJSON* ToJSON(bool withSensitiveData) const override; 26 | }; 27 | } // namespace OpenShock::Config 28 | -------------------------------------------------------------------------------- /include/config/WiFiCredentials.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config/ConfigBase.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace OpenShock::Config { 9 | struct WiFiCredentials : public ConfigBase { 10 | WiFiCredentials(); 11 | WiFiCredentials(uint8_t id, std::string_view ssid, std::string_view password); 12 | 13 | uint8_t id; 14 | std::string ssid; 15 | std::string password; 16 | 17 | void ToDefault() override; 18 | 19 | bool FromFlatbuffers(const Serialization::Configuration::WiFiCredentials* config) override; 20 | [[nodiscard]] flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const override; 21 | 22 | bool FromJSON(const cJSON* json) override; 23 | [[nodiscard]] cJSON* ToJSON(bool withSensitiveData) const override; 24 | }; 25 | } // namespace OpenShock::Config 26 | -------------------------------------------------------------------------------- /include/estop/EStopManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "estop/EStopState.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace OpenShock::EStopManager { 10 | [[nodiscard]] bool Init(); 11 | bool SetEStopEnabled(bool enabled); 12 | bool SetEStopPin(gpio_num_t pin); 13 | bool IsEStopped(); 14 | int64_t LastEStopped(); 15 | 16 | void Trigger(); 17 | } // namespace OpenShock::EStopManager 18 | -------------------------------------------------------------------------------- /include/estop/EStopState.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace OpenShock { 6 | enum class EStopState : uint8_t { 7 | Idle, 8 | Active, 9 | ActiveClearing, 10 | AwaitingRelease 11 | }; 12 | } // namespace OpenShock 13 | -------------------------------------------------------------------------------- /include/events/Events.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | ESP_EVENT_DECLARE_BASE(OPENSHOCK_EVENTS); 10 | 11 | enum { 12 | OPENSHOCK_EVENT_ESTOP_STATE_CHANGED, // Event for when the EStop activation state changes 13 | OPENSHOCK_EVENT_GATEWAY_CLIENT_STATE_CHANGED, // Event for when the gateway connection state changes 14 | }; 15 | 16 | #ifdef __cplusplus 17 | } 18 | #endif 19 | 20 | namespace OpenShock::Events { 21 | bool Init(); 22 | } 23 | -------------------------------------------------------------------------------- /include/http/JsonAPI.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "http/HTTPRequestManager.h" 4 | #include "serialization/JsonAPI.h" 5 | 6 | #include 7 | 8 | namespace OpenShock::HTTP::JsonAPI { 9 | /// @brief Links the hub to the account with the given account link code, returns the hub token. Valid response codes: 200, 404 10 | /// @param hubToken 11 | /// @return 12 | HTTP::Response LinkAccount(std::string_view accountLinkCode); 13 | 14 | /// @brief Gets the hub info for the given hub token. Valid response codes: 200, 401 15 | /// @param hubToken 16 | /// @return 17 | HTTP::Response GetHubInfo(std::string_view hubToken); 18 | 19 | /// @brief Requests a Live Control Gateway to connect to. Valid response codes: 200, 401 20 | /// @param hubToken 21 | /// @return 22 | HTTP::Response AssignLcg(std::string_view hubToken); 23 | } // namespace OpenShock::HTTP::JsonAPI 24 | -------------------------------------------------------------------------------- /include/message_handlers/WebSocket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace OpenShock::MessageHandlers::WebSocket { 6 | void HandleGatewayBinary(const uint8_t* data, std::size_t len); 7 | void HandleLocalBinary(uint8_t socketId, const uint8_t* data, std::size_t len); 8 | } 9 | -------------------------------------------------------------------------------- /include/message_handlers/impl/WSGateway.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serialization/_fbs/GatewayToHubMessage_generated.h" 4 | 5 | #include 6 | 7 | #define HANDLER_SIG(NAME) void NAME(const OpenShock::Serialization::Gateway::GatewayToHubMessage* msg) 8 | #define HANDLER_FN(NAME) HANDLER_SIG(Handle##NAME) 9 | 10 | namespace OpenShock::MessageHandlers::Server::_Private { 11 | typedef HANDLER_SIG((*HandlerType)); 12 | HANDLER_FN(Ping); 13 | HANDLER_FN(Trigger); 14 | HANDLER_FN(ShockerCommandList); 15 | HANDLER_FN(OtaUpdateRequest); 16 | HANDLER_FN(InvalidMessage); 17 | } // namespace OpenShock::MessageHandlers::Server::_Private 18 | 19 | #undef HANDLER_FN 20 | #undef HANDLER_SIG 21 | -------------------------------------------------------------------------------- /include/message_handlers/impl/WSLocal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serialization/_fbs/HubToLocalMessage_generated.h" 4 | #include "serialization/_fbs/LocalToHubMessage_generated.h" 5 | 6 | #include 7 | 8 | #define HANDLER_SIG(NAME) void NAME(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* msg) 9 | #define HANDLER_FN(NAME) HANDLER_SIG(Handle##NAME) 10 | 11 | namespace OpenShock::MessageHandlers::Local::_Private { 12 | typedef HANDLER_SIG((*HandlerType)); 13 | HANDLER_FN(WifiScanCommand); 14 | HANDLER_FN(WifiNetworkSaveCommand); 15 | HANDLER_FN(WifiNetworkForgetCommand); 16 | HANDLER_FN(WifiNetworkConnectCommand); 17 | HANDLER_FN(WifiNetworkDisconnectCommand); 18 | HANDLER_FN(OtaUpdateSetIsEnabledCommand); 19 | HANDLER_FN(OtaUpdateSetDomainCommand); 20 | HANDLER_FN(OtaUpdateSetUpdateChannelCommand); 21 | HANDLER_FN(OtaUpdateSetCheckIntervalCommand); 22 | HANDLER_FN(OtaUpdateSetAllowBackendManagementCommand); 23 | HANDLER_FN(OtaUpdateSetRequireManualApprovalCommand); 24 | HANDLER_FN(OtaUpdateHandleUpdateRequestCommand); 25 | HANDLER_FN(OtaUpdateCheckForUpdatesCommand); 26 | HANDLER_FN(OtaUpdateStartUpdateCommand); 27 | HANDLER_FN(AccountLinkCommand); 28 | HANDLER_FN(AccountUnlinkCommand); 29 | HANDLER_FN(SetRfTxPinCommand); 30 | HANDLER_FN(SetEstopEnabledCommand); 31 | HANDLER_FN(SetEstopPinCommand); 32 | HANDLER_FN(InvalidMessage); 33 | } // namespace OpenShock::MessageHandlers::Local::_Private 34 | 35 | #undef HANDLER_FN 36 | #undef HANDLER_SIG 37 | -------------------------------------------------------------------------------- /include/radio/RFTransmitter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | #include "ShockerCommandType.h" 5 | #include "ShockerModelType.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace OpenShock { 16 | class RFTransmitter { 17 | DISABLE_COPY(RFTransmitter); 18 | DISABLE_MOVE(RFTransmitter); 19 | 20 | public: 21 | RFTransmitter(gpio_num_t gpioPin); 22 | ~RFTransmitter(); 23 | 24 | inline gpio_num_t GetTxPin() const { return m_txPin; } 25 | 26 | inline bool ok() const { return m_rmtHandle != nullptr && m_queueHandle != nullptr && m_taskHandle != nullptr; } 27 | 28 | bool SendCommand(ShockerModelType model, uint16_t shockerId, ShockerCommandType type, uint8_t intensity, uint16_t durationMs, bool overwriteExisting = true); 29 | void ClearPendingCommands(); 30 | 31 | private: 32 | void destroy(); 33 | void TransmitTask(); 34 | 35 | gpio_num_t m_txPin; 36 | rmt_obj_t* m_rmtHandle; 37 | QueueHandle_t m_queueHandle; 38 | TaskHandle_t m_taskHandle; 39 | }; 40 | } // namespace OpenShock 41 | -------------------------------------------------------------------------------- /include/radio/rmt/CaiXianlinEncoder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ShockerCommandType.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace OpenShock::Rmt::CaiXianlinEncoder { 10 | size_t GetBufferSize(); 11 | bool FillBuffer(rmt_data_t* data, uint16_t shockerId, uint8_t channelId, ShockerCommandType type, uint8_t intensity); 12 | } 13 | -------------------------------------------------------------------------------- /include/radio/rmt/Petrainer998DREncoder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ShockerCommandType.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace OpenShock::Rmt::Petrainer998DREncoder { 10 | size_t GetBufferSize(); 11 | bool FillBuffer(rmt_data_t* data, uint16_t shockerId, ShockerCommandType type, uint8_t intensity); 12 | } 13 | -------------------------------------------------------------------------------- /include/radio/rmt/PetrainerEncoder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ShockerCommandType.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace OpenShock::Rmt::PetrainerEncoder { 10 | size_t GetBufferSize(); 11 | bool FillBuffer(rmt_data_t* data, uint16_t shockerId, ShockerCommandType type, uint8_t intensity); 12 | } 13 | -------------------------------------------------------------------------------- /include/radio/rmt/T330Encoder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ShockerCommandType.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace OpenShock::Rmt::T330Encoder { 10 | size_t GetBufferSize(); 11 | bool FillBuffer(rmt_data_t* data, uint16_t shockerId, ShockerCommandType type, uint8_t intensity); 12 | } 13 | -------------------------------------------------------------------------------- /include/radio/rmt/internal/Shared.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace OpenShock::Rmt::Internal { 10 | template 11 | constexpr void EncodeBits(rmt_data_t* sequence, T data, const rmt_data_t& rmtOne, const rmt_data_t& rmtZero) 12 | { 13 | static_assert(std::is_unsigned_v, "T must be an unsigned integer"); 14 | static_assert(N > 0, "N must be greater than 0"); 15 | static_assert(N < std::numeric_limits::digits, "N must be less or equal to the number of bits in T"); 16 | 17 | for (size_t i = 0; i < N; ++i) { 18 | size_t bit_pos = N - (i + 1); 19 | sequence[i] = (data >> bit_pos) & 1 ? rmtOne : rmtZero; 20 | } 21 | } 22 | } // namespace OpenShock::Rmt::Internal 23 | -------------------------------------------------------------------------------- /include/serial/SerialInputHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace OpenShock::SerialInputHandler { 6 | [[nodiscard]] bool Init(); 7 | 8 | bool SerialEchoEnabled(); 9 | void SetSerialEchoEnabled(bool enabled); 10 | 11 | void PrintWelcomeHeader(); 12 | void PrintVersionInfo(); 13 | } // namespace OpenShock::SerialInputHandler 14 | -------------------------------------------------------------------------------- /include/serial/command_handlers/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serial/command_handlers/index.h" 4 | 5 | #include "Logging.h" 6 | 7 | #include 8 | 9 | #define SERPR_SYS(format, ...) ::Serial.printf("$SYS$|" format "\r\n", ##__VA_ARGS__) 10 | #define SERPR_RESPONSE(format, ...) SERPR_SYS("Response|" format, ##__VA_ARGS__) 11 | #define SERPR_SUCCESS(format, ...) SERPR_SYS("Success|" format, ##__VA_ARGS__) 12 | #define SERPR_ERROR(format, ...) SERPR_SYS("Error|" format, ##__VA_ARGS__) 13 | 14 | using namespace std::string_view_literals; 15 | -------------------------------------------------------------------------------- /include/serial/command_handlers/index.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serial/command_handlers/CommandEntry.h" 4 | 5 | #include 6 | 7 | namespace OpenShock::Serial::CommandHandlers { 8 | OpenShock::Serial::CommandGroup VersionHandler(); 9 | OpenShock::Serial::CommandGroup RestartHandler(); 10 | OpenShock::Serial::CommandGroup SysInfoHandler(); 11 | OpenShock::Serial::CommandGroup EchoHandler(); 12 | OpenShock::Serial::CommandGroup ValidGpiosHandler(); 13 | OpenShock::Serial::CommandGroup RfTxPinHandler(); 14 | OpenShock::Serial::CommandGroup EStopHandler(); 15 | OpenShock::Serial::CommandGroup DomainHandler(); 16 | OpenShock::Serial::CommandGroup AuthTokenHandler(); 17 | OpenShock::Serial::CommandGroup HostnameHandler(); 18 | OpenShock::Serial::CommandGroup NetworksHandler(); 19 | OpenShock::Serial::CommandGroup KeepAliveHandler(); 20 | OpenShock::Serial::CommandGroup JsonConfigHandler(); 21 | OpenShock::Serial::CommandGroup RawConfigHandler(); 22 | OpenShock::Serial::CommandGroup RfTransmitHandler(); 23 | OpenShock::Serial::CommandGroup FactoryResetHandler(); 24 | 25 | inline std::vector AllCommandHandlers() 26 | { 27 | return { 28 | VersionHandler(), 29 | RestartHandler(), 30 | SysInfoHandler(), 31 | EchoHandler(), 32 | ValidGpiosHandler(), 33 | RfTxPinHandler(), 34 | EStopHandler(), 35 | DomainHandler(), 36 | AuthTokenHandler(), 37 | HostnameHandler(), 38 | NetworksHandler(), 39 | KeepAliveHandler(), 40 | JsonConfigHandler(), 41 | RawConfigHandler(), 42 | RfTransmitHandler(), 43 | FactoryResetHandler(), 44 | }; 45 | } 46 | } // namespace OpenShock::Serial::CommandHandlers 47 | -------------------------------------------------------------------------------- /include/serialization/CallbackFn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace OpenShock::Serialization::Common { 7 | typedef std::function SerializationCallbackFn; 8 | } 9 | -------------------------------------------------------------------------------- /include/serialization/JsonAPI.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ShockerModelType.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace OpenShock::Serialization::JsonAPI { 12 | struct LcgInstanceDetailsResponse { 13 | std::string name; 14 | std::string version; 15 | std::string currentTime; 16 | std::string countryCode; 17 | std::string fqdn; 18 | }; 19 | struct BackendVersionResponse { 20 | std::string version; 21 | std::string commit; 22 | std::string currentTime; 23 | }; 24 | struct AccountLinkResponse { 25 | std::string authToken; 26 | }; 27 | struct HubInfoResponse { 28 | std::string hubId; 29 | std::string hubName; 30 | struct ShockerInfo { 31 | std::string id; 32 | uint16_t rfId; 33 | OpenShock::ShockerModelType model; 34 | }; 35 | std::vector shockers; 36 | }; 37 | struct AssignLcgResponse { 38 | std::string host; 39 | uint16_t port; 40 | std::string path; 41 | std::string country; 42 | }; 43 | 44 | bool ParseLcgInstanceDetailsJsonResponse(int code, const cJSON* root, LcgInstanceDetailsResponse& out); 45 | bool ParseBackendVersionJsonResponse(int code, const cJSON* root, BackendVersionResponse& out); 46 | bool ParseAccountLinkJsonResponse(int code, const cJSON* root, AccountLinkResponse& out); 47 | bool ParseHubInfoJsonResponse(int code, const cJSON* root, HubInfoResponse& out); 48 | bool ParseAssignLcgJsonResponse(int code, const cJSON* root, AssignLcgResponse& out); 49 | } // namespace OpenShock::Serialization::JsonAPI 50 | -------------------------------------------------------------------------------- /include/serialization/JsonSerial.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ShockerModelType.h" 4 | #include "ShockerCommandType.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace OpenShock::Serialization::JsonSerial { 11 | struct ShockerCommand { 12 | OpenShock::ShockerModelType model; 13 | uint16_t id; 14 | OpenShock::ShockerCommandType command; 15 | uint8_t intensity; 16 | uint16_t durationMs; 17 | }; 18 | 19 | bool ParseShockerCommand(const cJSON* root, ShockerCommand& out); 20 | } // namespace OpenShock::Serialization::JsonAPI 21 | -------------------------------------------------------------------------------- /include/serialization/WSGateway.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FirmwareBootType.h" 4 | #include "SemVer.h" 5 | #include "serialization/CallbackFn.h" 6 | 7 | #include "serialization/_fbs/HubToGatewayMessage_generated.h" 8 | 9 | #include 10 | 11 | #define SERIALIZER_FN(NAME, ...) bool Serialize##NAME##Message(__VA_ARGS__ __VA_OPT__(, ) Common::SerializationCallbackFn callback) 12 | 13 | namespace OpenShock::Serialization::Gateway { 14 | SERIALIZER_FN(Pong); 15 | SERIALIZER_FN(BootStatus, int32_t updateId, OpenShock::FirmwareBootType bootType); 16 | SERIALIZER_FN(OtaUpdateStarted, int32_t updateId, const OpenShock::SemVer& version); 17 | SERIALIZER_FN(OtaUpdateProgress, int32_t updateId, Types::OtaUpdateProgressTask task, float progress); 18 | SERIALIZER_FN(OtaUpdateFailed, int32_t updateId, std::string_view message, bool fatal); 19 | } // namespace OpenShock::Serialization::Gateway 20 | 21 | #undef SERIALZIER_FN 22 | -------------------------------------------------------------------------------- /include/serialization/WSLocal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serialization/CallbackFn.h" 4 | #include "wifi/WiFiScanStatus.h" 5 | 6 | #include "serialization/_fbs/WifiNetworkEventType_generated.h" 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace OpenShock { 13 | class WiFiNetwork; 14 | } 15 | 16 | namespace OpenShock::Serialization::Local { 17 | bool SerializeErrorMessage(std::string_view message, Common::SerializationCallbackFn callback); 18 | bool SerializeReadyMessage(const WiFiNetwork* connectedNetwork, bool accountLinked, Common::SerializationCallbackFn callback); 19 | bool SerializeWiFiScanStatusChangedEvent(OpenShock::WiFiScanStatus status, Common::SerializationCallbackFn callback); 20 | bool SerializeWiFiNetworkEvent(Types::WifiNetworkEventType eventType, const WiFiNetwork& network, Common::SerializationCallbackFn callback); 21 | bool SerializeWiFiNetworksEvent(Types::WifiNetworkEventType eventType, const std::vector& networks, Common::SerializationCallbackFn callback); 22 | } // namespace OpenShock::Serialization::Local 23 | -------------------------------------------------------------------------------- /include/util/CertificateUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace OpenShock::CertificateUtils { 9 | WiFiClientSecure GetSecureClient(); 10 | bool GetHostCertificate(const char* host, std::vector& pem); 11 | } // namespace OpenShock::CertificateUtils 12 | -------------------------------------------------------------------------------- /include/util/DigitCounter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace OpenShock::Util { 8 | template 9 | inline constexpr int Digits10CountMax = std::numeric_limits::digits10 + (std::is_signed_v ? 2 : 1); 10 | 11 | template 12 | constexpr std::size_t Digits10Count(T val) 13 | { 14 | static_assert(std::is_integral_v); 15 | 16 | std::size_t digits = 1; 17 | 18 | if constexpr (std::is_signed_v && val < 0) { 19 | digits++; 20 | val = -val; 21 | } 22 | 23 | while (val >= 10) { 24 | val /= 10; 25 | digits++; 26 | } 27 | 28 | return digits; 29 | } 30 | } // namespace OpenShock::Util 31 | -------------------------------------------------------------------------------- /include/util/FnProxy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace OpenShock::Util { 7 | namespace FnProxyImpl { 8 | template 9 | class MemFunTraits; 10 | 11 | template 12 | struct MemFunTraits { 13 | constexpr static const std::size_t arity = sizeof...(Ts); 14 | using ReturnType = R; 15 | using Class = C; 16 | }; 17 | } // namespace FnProxyImpl 18 | 19 | // Proxies a member function pointer to a void(void*) function pointer, allowing it to be passed to C-style APIs that expect a callback. 20 | template 21 | inline void FnProxy(void* p, Args... args) { 22 | using T = decltype(MemberFunction); 23 | using C = typename FnProxyImpl::MemFunTraits::Class; 24 | (reinterpret_cast(p)->*MemberFunction)(std::forward(args)...); 25 | } 26 | } // namespace OpenShock::Util 27 | -------------------------------------------------------------------------------- /include/util/IPAddressUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace OpenShock { 8 | bool IPV4AddressFromStringView(IPAddress& ip, std::string_view sv); 9 | } 10 | -------------------------------------------------------------------------------- /include/util/PartitionUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace OpenShock { 10 | bool TryGetPartitionHash(const esp_partition_t* partition, char (&hash)[65]); 11 | bool FlashPartitionFromUrl(const esp_partition_t* partition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32], std::function progressCallback = nullptr); 12 | } 13 | -------------------------------------------------------------------------------- /include/util/TaskUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace OpenShock::TaskUtils { 8 | /// @brief Create a task on the specified core, or the default core if the specified core is invalid 9 | BaseType_t TaskCreateUniversal(TaskFunction_t pvTaskCode, const char* const pcName, const uint32_t usStackDepth, void* const pvParameters, UBaseType_t uxPriority, TaskHandle_t* const pvCreatedTask, const BaseType_t xCoreID); 10 | 11 | /// @brief Create a task on the core that does expensive work, this should not run on the core that handles WiFi 12 | BaseType_t TaskCreateExpensive(TaskFunction_t pvTaskCode, const char* const pcName, const uint32_t usStackDepth, void* const pvParameters, UBaseType_t uxPriority, TaskHandle_t* const pvCreatedTask); 13 | } // namespace OpenShock::TaskUtils 14 | -------------------------------------------------------------------------------- /include/wifi/WiFiNetwork.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace OpenShock { 9 | struct WiFiNetwork { 10 | WiFiNetwork(); 11 | WiFiNetwork(const wifi_ap_record_t* apRecord, uint8_t credentialsId); 12 | WiFiNetwork(const char (&ssid)[33], const uint8_t (&bssid)[6], uint8_t channel, int8_t rssi, wifi_auth_mode_t authMode, uint8_t credentialsId); 13 | WiFiNetwork(const uint8_t (&ssid)[33], const uint8_t (&bssid)[6], uint8_t channel, int8_t rssi, wifi_auth_mode_t authMode, uint8_t credentialsId); 14 | 15 | std::array GetHexBSSID() const; 16 | bool IsSaved() const; 17 | 18 | char ssid[33]; 19 | uint8_t bssid[6]; 20 | uint8_t channel; 21 | int8_t rssi; 22 | wifi_auth_mode_t authMode; 23 | uint8_t credentialsID; 24 | uint16_t connectAttempts; // TODO: Add connectSuccesses as well, so we can track the success rate of a network 25 | int64_t lastConnectAttempt; 26 | uint8_t scansMissed; 27 | }; 28 | } // namespace OpenShock 29 | -------------------------------------------------------------------------------- /include/wifi/WiFiScanManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "wifi/WiFiScanStatus.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace OpenShock::WiFiScanManager { 11 | [[nodiscard]] bool Init(); 12 | 13 | bool IsScanning(); 14 | 15 | bool StartScan(); 16 | bool AbortScan(); 17 | 18 | typedef std::function StatusChangedHandler; 19 | typedef std::function& networkRecords)> NetworksDiscoveredHandler; 20 | 21 | uint64_t RegisterStatusChangedHandler(const StatusChangedHandler& handler); 22 | void UnregisterStatusChangedHandler(uint64_t id); 23 | 24 | uint64_t RegisterNetworksDiscoveredHandler(const NetworksDiscoveredHandler& handler); 25 | void UnregisterNetworksDiscoveredHandler(uint64_t id); 26 | } // namespace OpenShock::WiFiScanManager 27 | -------------------------------------------------------------------------------- /include/wifi/WiFiScanStatus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "serialization/_fbs/WifiScanStatus_generated.h" 4 | 5 | #include 6 | 7 | namespace OpenShock { 8 | typedef OpenShock::Serialization::Types::WifiScanStatus WiFiScanStatus; 9 | } // namespace OpenShock 10 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenShock/Firmware/70dd3619e14ff939025a137e5db1defdd00131bc/requirements.txt -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | -------------------------------------------------------------------------------- /scripts/flatc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenShock/Firmware/70dd3619e14ff939025a137e5db1defdd00131bc/scripts/flatc -------------------------------------------------------------------------------- /scripts/flatc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenShock/Firmware/70dd3619e14ff939025a137e5db1defdd00131bc/scripts/flatc.exe -------------------------------------------------------------------------------- /scripts/install_dependencies.py: -------------------------------------------------------------------------------- 1 | import importlib.util 2 | import sys 3 | import subprocess 4 | 5 | class Package: 6 | def __init__(self, pip_name, pip_options, import_name): 7 | self.pip_name = pip_name 8 | self.pip_options = pip_options 9 | self.import_name = import_name 10 | 11 | def pip_name(self): 12 | return self.pip_name 13 | 14 | def pip_package(self): 15 | if len(self.pip_options) == 0: 16 | return self.pip_name 17 | 18 | return self.pip_name + '[' + ','.join(self.pip_options) + ']' 19 | 20 | def import_name(self): 21 | return self.import_name 22 | 23 | def is_installed(self): 24 | if self.pip_name in sys.modules: 25 | return True 26 | 27 | if importlib.util.find_spec(self.pip_name) is not None: 28 | return True 29 | 30 | try: 31 | __import__(self.import_name) 32 | return True 33 | except ImportError: 34 | pass 35 | 36 | return False 37 | 38 | # List of packages to install (pip name, package name) 39 | required = [ 40 | # Captive Portal building 41 | Package('fonttools', ['woff', 'unicode'], 'fontTools'), 42 | Package('brotli', [], 'brotli'), 43 | Package('gitpython', [], 'git'), 44 | # esptool (while using custom version) 45 | Package('intelhex', [], "IntelHex"), 46 | ] 47 | 48 | # Get all packages that are not installed 49 | to_install = [ p.pip_package() for p in required if not p.is_installed() ] 50 | 51 | # Install all packages that are not installed 52 | if len(to_install) > 0: 53 | print('Installing: ' + ', '.join(to_install)) 54 | 55 | # Get the python executable path 56 | python_path = sys.executable 57 | 58 | # Install the packages 59 | subprocess.check_call([python_path, '-m', 'pip', 'install', *to_install]) 60 | -------------------------------------------------------------------------------- /scripts/merge_image.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | import os 4 | import sys 5 | from pathlib import Path 6 | from configparser import ConfigParser 7 | from utils.boardconf import BoardConf, from_pio_file, validate, print_header, print_footer 8 | 9 | 10 | def call_script(file): 11 | print('Calling script: %s' % file) 12 | print() 13 | with open(file) as f: 14 | exec(f.read()) 15 | sys.exit(0) 16 | 17 | 18 | # Get pio env name from CLI args 19 | env_name = sys.argv[1] 20 | 21 | # Read board configuration from platformio.ini 22 | boardconf: BoardConf = from_pio_file(env_name) 23 | 24 | 25 | def merge_image(): 26 | if not validate(boardconf): 27 | return 28 | 29 | # If the board exists directly, call `merge-image.py` in that directory. 30 | if not boardconf.does_merge_script_exist(): 31 | print('ERROR: MERGE SCRIPT DOES NOT EXIST: %s' % boardconf.get_merge_script()) 32 | print('FAILED TO FIND merge-image.py') 33 | sys.exit(1) 34 | 35 | # Call the chips/{chip}/merge-image.py script 36 | call_script(boardconf.get_merge_script()) 37 | 38 | 39 | print_header() 40 | merge_image() 41 | print_footer() 42 | -------------------------------------------------------------------------------- /scripts/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenShock/Firmware/70dd3619e14ff939025a137e5db1defdd00131bc/scripts/utils/__init__.py -------------------------------------------------------------------------------- /scripts/utils/conv.py: -------------------------------------------------------------------------------- 1 | BOOLEAN_MAP = { 2 | 'true': True, 3 | 'false': False, 4 | '1': True, 5 | '0': False, 6 | } 7 | 8 | 9 | def to_bool(value: str | None) -> bool: 10 | if value == None: 11 | return False 12 | value = value.lower() 13 | 14 | b = BOOLEAN_MAP.get(value) 15 | if b == None: 16 | raise ValueError('Value cannot be interpreted as boolean value: %s' % value) 17 | return b 18 | 19 | 20 | def to_int(value: str | None) -> int: 21 | if value == None: 22 | return False 23 | return int(value) 24 | 25 | 26 | def to_string(value: str | None) -> str: 27 | if value == None: 28 | raise ValueError('Value cannot be interpreted as string value: %s' % value) 29 | return value 30 | -------------------------------------------------------------------------------- /scripts/utils/shorthands.py: -------------------------------------------------------------------------------- 1 | import os 2 | from . import sysenv 3 | 4 | 5 | # The short ref name of the branch or tag that triggered the workflow run. 6 | # This value matches the branch or tag name shown on GitHub. For example, `feature-branch-1`. 7 | def get_github_ref_name(): 8 | return sysenv.get_string('GITHUB_REF_NAME', '') 9 | 10 | 11 | # The name of the base ref or target branch of the pull request in a workflow run. 12 | # This is only set when the event that triggers a workflow run is either `pull_request` 13 | # or `pull_request_target`. For example, `main`. 14 | def get_github_base_ref(): 15 | return sysenv.get_string('GITHUB_BASE_REF', '') 16 | 17 | 18 | # The name of the event that triggered the workflow. For example, `workflow_dispatch`. 19 | def get_github_event_name(): 20 | return sysenv.get_string('GITHUB_EVENT_NAME', '') 21 | 22 | 23 | # Whether the current environment is a Github CI environment. 24 | def is_github_ci(): 25 | return sysenv.get_bool('CI', False) and sysenv.get_bool('GITHUB_ACTIONS', False) 26 | 27 | 28 | # Whether the current environment, assuming is_github_ci() == True, is caused by a pull request event. 29 | def is_github_pr(): 30 | return get_github_event_name() == 'pull_request' 31 | 32 | 33 | # Checks whether the event is a pull_request with the specified branch as base_ref. 34 | def is_github_pr_into(branch: str) -> bool: 35 | return is_github_pr() and get_github_base_ref() == branch 36 | 37 | 38 | # Checks if the run was triggered by a tag. 39 | def is_github_tag() -> bool: 40 | return sysenv.get_string('GITHUB_REF_TYPE', 'branch') == 'tag' 41 | 42 | def get_latest_tag(): 43 | # Use git to get the latest tag. 44 | return os.popen('git describe --tags --abbrev=0').read().strip() -------------------------------------------------------------------------------- /scripts/utils/sysenv.py: -------------------------------------------------------------------------------- 1 | import os 2 | from . import conv 3 | 4 | 5 | def get_bool(key: str, default: bool | None = None) -> bool: 6 | try: 7 | return conv.to_bool(os.environ.get(key)) 8 | except Exception as ex: 9 | if default != None: 10 | return default 11 | raise ValueError('Failed to get environment boolean: %s' % key) from ex 12 | 13 | 14 | def get_string(key: str, default: str | None = None) -> str: 15 | try: 16 | return conv.to_string(os.environ.get(key)) 17 | except Exception as ex: 18 | if default != None: 19 | return default 20 | raise ValueError('Failed to get environment string: %s' % key) from ex 21 | 22 | 23 | def get_all_prefixed(prefix: str) -> dict[str, str]: 24 | result = {} 25 | for key, value in os.environ.items(): 26 | if key.startswith(prefix): 27 | result[key] = value 28 | return result 29 | -------------------------------------------------------------------------------- /src/CompatibilityChecks.cpp: -------------------------------------------------------------------------------- 1 | #include "Common.h" 2 | #include "Chipset.h" 3 | 4 | const bool kIsValidOrUndefinedRfTxPin = OpenShock::IsValidOutputPin(OPENSHOCK_RF_TX_GPIO) || OPENSHOCK_RF_TX_GPIO == OPENSHOCK_GPIO_INVALID; 5 | static_assert(kIsValidOrUndefinedRfTxPin , "OPENSHOCK_RF_TX_GPIO is not a valid output GPIO, and is not declared as bypassed by board specific definitions, refusing to compile"); 6 | 7 | const bool kIsValidOrUndefinedEStopPin = OpenShock::IsValidInputPin(OPENSHOCK_ESTOP_PIN) || OPENSHOCK_ESTOP_PIN == OPENSHOCK_GPIO_INVALID; 8 | static_assert(kIsValidOrUndefinedEStopPin, "OPENSHOCK_ESTOP_PIN is not a valid input GPIO, and is not declared as bypassed by board specific definitions, refusing to compile"); 9 | 10 | #ifdef OPENSHOCK_LED_GPIO 11 | static_assert(OpenShock::IsValidOutputPin(OPENSHOCK_LED_GPIO), "OPENSHOCK_LED_GPIO is not a valid output GPIO, and is not declared as bypassed by board specific definitions, refusing to compile"); 12 | #endif 13 | 14 | #ifdef OPENSHOCK_LED_WS2812B 15 | static_assert(OpenShock::IsValidOutputPin(OPENSHOCK_LED_WS2812B), "OPENSHOCK_LED_WS2812B is not a valid output GPIO, and is not declared as bypassed by board specific definitions, refusing to compile"); 16 | #endif 17 | -------------------------------------------------------------------------------- /src/ReadWriteMutex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ReadWriteMutex.h" 4 | 5 | const char* const TAG = "ReadWriteMutex"; 6 | 7 | #include "Logging.h" 8 | 9 | OpenShock::ReadWriteMutex::ReadWriteMutex() : m_mutex(xSemaphoreCreateMutex()), m_readSem(xSemaphoreCreateBinary()), m_readers(0) { 10 | xSemaphoreGive(m_readSem); 11 | } 12 | 13 | OpenShock::ReadWriteMutex::~ReadWriteMutex() { 14 | vSemaphoreDelete(m_mutex); 15 | vSemaphoreDelete(m_readSem); 16 | } 17 | 18 | bool OpenShock::ReadWriteMutex::lockRead(TickType_t xTicksToWait) { 19 | if (xSemaphoreTake(m_readSem, xTicksToWait) == pdFALSE) { 20 | OS_LOGE(TAG, "Failed to take read semaphore"); 21 | return false; 22 | } 23 | 24 | if (++m_readers == 1) { 25 | if (xSemaphoreTake(m_mutex, xTicksToWait) == pdFALSE) { 26 | xSemaphoreGive(m_readSem); 27 | return false; 28 | } 29 | } 30 | 31 | xSemaphoreGive(m_readSem); 32 | 33 | return true; 34 | } 35 | 36 | void OpenShock::ReadWriteMutex::unlockRead() { 37 | if (xSemaphoreTake(m_readSem, portMAX_DELAY) == pdFALSE) { 38 | OS_LOGE(TAG, "Failed to take read semaphore"); 39 | return; 40 | } 41 | 42 | if (--m_readers == 0) { 43 | xSemaphoreGive(m_mutex); 44 | } 45 | 46 | xSemaphoreGive(m_readSem); 47 | } 48 | 49 | bool OpenShock::ReadWriteMutex::lockWrite(TickType_t xTicksToWait) { 50 | if (xSemaphoreTake(m_mutex, xTicksToWait) == pdFALSE) { 51 | OS_LOGE(TAG, "Failed to take mutex"); 52 | return false; 53 | } 54 | 55 | return true; 56 | } 57 | 58 | void OpenShock::ReadWriteMutex::unlockWrite() { 59 | xSemaphoreGive(m_mutex); 60 | } 61 | -------------------------------------------------------------------------------- /src/SimpleMutex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "SimpleMutex.h" 4 | 5 | const char* const TAG = "SimpleMutex"; 6 | 7 | OpenShock::SimpleMutex::SimpleMutex() 8 | : m_mutex(xSemaphoreCreateMutex()) 9 | { 10 | } 11 | 12 | OpenShock::SimpleMutex::~SimpleMutex() 13 | { 14 | vSemaphoreDelete(m_mutex); 15 | } 16 | 17 | bool OpenShock::SimpleMutex::lock(TickType_t xTicksToWait) 18 | { 19 | return xSemaphoreTake(m_mutex, xTicksToWait) == pdTRUE; 20 | } 21 | 22 | void OpenShock::SimpleMutex::unlock() 23 | { 24 | xSemaphoreGive(m_mutex); 25 | } 26 | -------------------------------------------------------------------------------- /src/config/CaptivePortalConfig.cpp: -------------------------------------------------------------------------------- 1 | #include "config/CaptivePortalConfig.h" 2 | 3 | const char* const TAG = "Config::CaptivePortalConfig"; 4 | 5 | #include "config/internal/utils.h" 6 | #include "Logging.h" 7 | 8 | using namespace OpenShock::Config; 9 | 10 | CaptivePortalConfig::CaptivePortalConfig() 11 | : alwaysEnabled(false) 12 | { 13 | } 14 | 15 | CaptivePortalConfig::CaptivePortalConfig(bool alwaysEnabled) 16 | : alwaysEnabled(alwaysEnabled) 17 | { 18 | } 19 | 20 | void CaptivePortalConfig::ToDefault() { 21 | alwaysEnabled = false; 22 | } 23 | 24 | bool CaptivePortalConfig::FromFlatbuffers(const Serialization::Configuration::CaptivePortalConfig* config) { 25 | if (config == nullptr) { 26 | OS_LOGW(TAG, "Config is null, setting to default"); 27 | ToDefault(); 28 | return true; 29 | } 30 | 31 | alwaysEnabled = config->always_enabled(); 32 | 33 | return true; 34 | } 35 | 36 | flatbuffers::Offset CaptivePortalConfig::ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const { 37 | return Serialization::Configuration::CreateCaptivePortalConfig(builder, alwaysEnabled); 38 | } 39 | 40 | bool CaptivePortalConfig::FromJSON(const cJSON* json) { 41 | if (json == nullptr) { 42 | OS_LOGW(TAG, "Config is null, setting to default"); 43 | ToDefault(); 44 | return true; 45 | } 46 | 47 | if (cJSON_IsObject(json) == 0) { 48 | OS_LOGE(TAG, "json is not an object"); 49 | return false; 50 | } 51 | 52 | Internal::Utils::FromJsonBool(alwaysEnabled, json, "alwaysEnabled", false); 53 | 54 | return true; 55 | } 56 | 57 | cJSON* CaptivePortalConfig::ToJSON(bool withSensitiveData) const { 58 | cJSON* root = cJSON_CreateObject(); 59 | 60 | cJSON_AddBoolToObject(root, "alwaysEnabled", alwaysEnabled); 61 | 62 | return root; 63 | } 64 | -------------------------------------------------------------------------------- /src/config/SerialInputConfig.cpp: -------------------------------------------------------------------------------- 1 | #include "config/SerialInputConfig.h" 2 | 3 | const char* const TAG = "Config::SerialInputConfig"; 4 | 5 | #include "config/internal/utils.h" 6 | #include "Logging.h" 7 | 8 | using namespace OpenShock::Config; 9 | 10 | SerialInputConfig::SerialInputConfig() 11 | : echoEnabled(true) 12 | { 13 | } 14 | 15 | SerialInputConfig::SerialInputConfig(bool echoEnabled) 16 | : echoEnabled(echoEnabled) 17 | { 18 | } 19 | 20 | void SerialInputConfig::ToDefault() { 21 | echoEnabled = true; 22 | } 23 | 24 | bool SerialInputConfig::FromFlatbuffers(const Serialization::Configuration::SerialInputConfig* config) { 25 | if (config == nullptr) { 26 | OS_LOGW(TAG, "Config is null, setting to default"); 27 | ToDefault(); 28 | return true; 29 | } 30 | 31 | echoEnabled = config->echo_enabled(); 32 | 33 | return true; 34 | } 35 | 36 | flatbuffers::Offset SerialInputConfig::ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const { 37 | return Serialization::Configuration::CreateSerialInputConfig(builder, echoEnabled); 38 | } 39 | 40 | bool SerialInputConfig::FromJSON(const cJSON* json) { 41 | if (json == nullptr) { 42 | OS_LOGW(TAG, "Config is null, setting to default"); 43 | ToDefault(); 44 | return true; 45 | } 46 | 47 | if (cJSON_IsObject(json) == 0) { 48 | OS_LOGE(TAG, "json is not an object"); 49 | return false; 50 | } 51 | 52 | Internal::Utils::FromJsonBool(echoEnabled, json, "echoEnabled", true); 53 | 54 | return true; 55 | } 56 | 57 | cJSON* SerialInputConfig::ToJSON(bool withSensitiveData) const { 58 | cJSON* root = cJSON_CreateObject(); 59 | 60 | cJSON_AddBoolToObject(root, "echoEnabled", echoEnabled); 61 | 62 | return root; 63 | } 64 | -------------------------------------------------------------------------------- /src/events/Events.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "events/Events.h" 4 | 5 | #include "Logging.h" 6 | 7 | const char* const TAG = "Events"; 8 | 9 | ESP_EVENT_DEFINE_BASE(OPENSHOCK_EVENTS); 10 | 11 | using namespace OpenShock; 12 | 13 | bool Events::Init() 14 | { 15 | esp_err_t err = esp_event_loop_create_default(); 16 | if (err != ESP_OK) { 17 | OS_LOGE(TAG, "Failed to create default event loop: %s", esp_err_to_name(err)); 18 | return false; 19 | } 20 | 21 | return true; 22 | } 23 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/gateway/OtaUpdateRequest.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSGateway.h" 2 | 3 | const char* const TAG = "ServerMessageHandlers"; 4 | 5 | #include "CaptivePortal.h" 6 | #include "Logging.h" 7 | #include "OtaUpdateManager.h" 8 | 9 | #include 10 | 11 | using namespace OpenShock::MessageHandlers::Server; 12 | 13 | void _Private::HandleOtaUpdateRequest(const OpenShock::Serialization::Gateway::GatewayToHubMessage* root) 14 | { 15 | auto msg = root->payload_as_OtaUpdateRequest(); 16 | if (msg == nullptr) { 17 | OS_LOGE(TAG, "Payload cannot be parsed as OtaUpdate"); 18 | return; 19 | } 20 | 21 | auto semver = msg->version(); 22 | if (semver == nullptr) { 23 | OS_LOGE(TAG, "Version cannot be parsed"); 24 | return; 25 | } 26 | 27 | std::string_view prerelease, build; 28 | if (semver->prerelease() != nullptr) { 29 | prerelease = std::string_view(semver->prerelease()->c_str(), semver->prerelease()->size()); 30 | } 31 | if (semver->build() != nullptr) { 32 | build = std::string_view(semver->build()->c_str(), semver->build()->size()); 33 | } 34 | 35 | OpenShock::SemVer version(semver->major(), semver->minor(), semver->patch(), prerelease, build); 36 | 37 | OS_LOGI(TAG, "OTA update requested for version %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this 38 | 39 | if (!OpenShock::OtaUpdateManager::TryStartFirmwareUpdate(version)) { 40 | OS_LOGE(TAG, "Failed to update firmware"); // TODO: Send error message to server 41 | return; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/gateway/Ping.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSGateway.h" 2 | 3 | const char* const TAG = "ServerMessageHandlers"; 4 | 5 | #include "GatewayConnectionManager.h" 6 | #include "Logging.h" 7 | #include "serialization/WSGateway.h" 8 | 9 | #include 10 | 11 | using namespace OpenShock::MessageHandlers::Server; 12 | 13 | void _Private::HandlePing(const OpenShock::Serialization::Gateway::GatewayToHubMessage* root) 14 | { 15 | auto msg = root->payload_as_Ping(); 16 | if (msg == nullptr) { 17 | OS_LOGE(TAG, "Payload cannot be parsed as Ping"); 18 | return; 19 | } 20 | 21 | Serialization::Gateway::SerializePongMessage(GatewayConnectionManager::SendMessageBIN); 22 | } 23 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/gateway/Trigger.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSGateway.h" 2 | 3 | const char* const TAG = "ServerMessageHandlers"; 4 | 5 | #include "CaptivePortal.h" 6 | #include "estop/EStopManager.h" 7 | #include "Logging.h" 8 | 9 | #include 10 | 11 | #include 12 | 13 | using namespace OpenShock::MessageHandlers::Server; 14 | 15 | using TriggerType = OpenShock::Serialization::Gateway::TriggerType; 16 | 17 | void _Private::HandleTrigger(const OpenShock::Serialization::Gateway::GatewayToHubMessage* root) 18 | { 19 | auto msg = root->payload_as_Trigger(); 20 | if (msg == nullptr) { 21 | OS_LOGE(TAG, "Payload cannot be parsed as Trigger"); 22 | return; 23 | } 24 | 25 | if (EStopManager::IsEStopped()) { 26 | OS_LOGD(TAG, "Ignoring trigger command due to EmergencyStop being activated"); 27 | return; 28 | } 29 | 30 | auto triggerType = msg->type(); 31 | 32 | switch (triggerType) { 33 | case TriggerType::Restart: 34 | esp_restart(); 35 | break; 36 | case TriggerType::EmergencyStop: 37 | EStopManager::Trigger(); 38 | break; 39 | case TriggerType::CaptivePortalEnable: 40 | OpenShock::CaptivePortal::SetAlwaysEnabled(true); 41 | break; 42 | case TriggerType::CaptivePortalDisable: 43 | OpenShock::CaptivePortal::SetAlwaysEnabled(false); 44 | break; 45 | default: 46 | OS_LOGW(TAG, "Got unknown trigger type: %hhu", static_cast(triggerType)); 47 | break; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/gateway/_InvalidMessage.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSGateway.h" 2 | 3 | const char* const TAG = "ServerMessageHandlers"; 4 | 5 | #include "Logging.h" 6 | 7 | using namespace OpenShock::MessageHandlers::Server; 8 | 9 | void _Private::HandleInvalidMessage(const OpenShock::Serialization::Gateway::GatewayToHubMessage* root) 10 | { 11 | if (root == nullptr) { 12 | OS_LOGE(TAG, "Message cannot be parsed"); 13 | return; 14 | } 15 | 16 | OS_LOGE(TAG, "Invalid message type: %u", root->payload_type()); 17 | } 18 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/AccountUnlinkCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | const char* const TAG = "LocalMessageHandlers"; 4 | 5 | #include "GatewayConnectionManager.h" 6 | #include "Logging.h" 7 | 8 | #include 9 | 10 | using namespace OpenShock::MessageHandlers::Local; 11 | 12 | void _Private::HandleAccountUnlinkCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 13 | { 14 | (void)socketId; 15 | 16 | auto msg = root->payload_as_AccountUnlinkCommand(); 17 | if (msg == nullptr) { 18 | OS_LOGE(TAG, "Payload cannot be parsed as AccountUnlinkCommand"); 19 | return; 20 | } 21 | 22 | GatewayConnectionManager::UnLink(); 23 | } 24 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/OtaUpdateCheckForUpdatesCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | #include "Logging.h" 4 | 5 | #include 6 | 7 | const char* const TAG = "LocalMessageHandlers"; 8 | 9 | using namespace OpenShock::MessageHandlers::Local; 10 | 11 | void _Private::HandleOtaUpdateCheckForUpdatesCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 12 | { 13 | auto msg = root->payload_as_OtaUpdateCheckForUpdatesCommand(); 14 | if (msg == nullptr) { 15 | OS_LOGE(TAG, "Payload cannot be parsed as OtaUpdateCheckForUpdatesCommand"); 16 | return; 17 | } 18 | 19 | // TODO 20 | } 21 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/OtaUpdateHandleUpdateRequestCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | #include "Logging.h" 4 | 5 | #include 6 | 7 | const char* const TAG = "LocalMessageHandlers"; 8 | 9 | using namespace OpenShock::MessageHandlers::Local; 10 | 11 | void _Private::HandleOtaUpdateHandleUpdateRequestCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 12 | { 13 | auto msg = root->payload_as_OtaUpdateHandleUpdateRequestCommand(); 14 | if (msg == nullptr) { 15 | OS_LOGE(TAG, "Payload cannot be parsed as OtaUpdateHandleUpdateRequestCommand"); 16 | return; 17 | } 18 | 19 | // TODO 20 | } 21 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/OtaUpdateSetAllowBackendManagementCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | #include "Logging.h" 4 | 5 | #include 6 | 7 | const char* const TAG = "LocalMessageHandlers"; 8 | 9 | using namespace OpenShock::MessageHandlers::Local; 10 | 11 | void _Private::HandleOtaUpdateSetAllowBackendManagementCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 12 | { 13 | auto msg = root->payload_as_OtaUpdateSetAllowBackendManagementCommand(); 14 | if (msg == nullptr) { 15 | OS_LOGE(TAG, "Payload cannot be parsed as OtaUpdateSetAllowBackendManagementCommand"); 16 | return; 17 | } 18 | 19 | // TODO 20 | } 21 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/OtaUpdateSetCheckIntervalCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | #include "Logging.h" 4 | 5 | #include 6 | 7 | const char* const TAG = "LocalMessageHandlers"; 8 | 9 | using namespace OpenShock::MessageHandlers::Local; 10 | 11 | void _Private::HandleOtaUpdateSetCheckIntervalCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 12 | { 13 | auto msg = root->payload_as_OtaUpdateSetCheckIntervalCommand(); 14 | if (msg == nullptr) { 15 | OS_LOGE(TAG, "Payload cannot be parsed as OtaUpdateSetCheckIntervalCommand"); 16 | return; 17 | } 18 | 19 | // TODO 20 | } 21 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/OtaUpdateSetDomainCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | #include "Logging.h" 4 | 5 | #include 6 | 7 | const char* const TAG = "LocalMessageHandlers"; 8 | 9 | using namespace OpenShock::MessageHandlers::Local; 10 | 11 | void _Private::HandleOtaUpdateSetDomainCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 12 | { 13 | auto msg = root->payload_as_OtaUpdateSetDomainCommand(); 14 | if (msg == nullptr) { 15 | OS_LOGE(TAG, "Payload cannot be parsed as OtaUpdateSetDomainCommand"); 16 | return; 17 | } 18 | 19 | // TODO 20 | } 21 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/OtaUpdateSetIsEnabledCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | #include "Logging.h" 4 | 5 | #include 6 | 7 | const char* const TAG = "LocalMessageHandlers"; 8 | 9 | using namespace OpenShock::MessageHandlers::Local; 10 | 11 | void _Private::HandleOtaUpdateSetIsEnabledCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 12 | { 13 | auto msg = root->payload_as_OtaUpdateSetIsEnabledCommand(); 14 | if (msg == nullptr) { 15 | OS_LOGE(TAG, "Payload cannot be parsed as OtaUpdateSetIsEnabledCommand"); 16 | return; 17 | } 18 | 19 | // TODO 20 | } 21 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/OtaUpdateSetRequireManualApprovalCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | #include "Logging.h" 4 | 5 | #include 6 | 7 | const char* const TAG = "LocalMessageHandlers"; 8 | 9 | using namespace OpenShock::MessageHandlers::Local; 10 | 11 | void _Private::HandleOtaUpdateSetRequireManualApprovalCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 12 | { 13 | auto msg = root->payload_as_OtaUpdateSetRequireManualApprovalCommand(); 14 | if (msg == nullptr) { 15 | OS_LOGE(TAG, "Payload cannot be parsed as OtaUpdateSetRequireManualApprovalCommand"); 16 | return; 17 | } 18 | 19 | // TODO 20 | } 21 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/OtaUpdateSetUpdateChannelCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | #include "Logging.h" 4 | 5 | #include 6 | 7 | const char* const TAG = "LocalMessageHandlers"; 8 | 9 | using namespace OpenShock::MessageHandlers::Local; 10 | 11 | void _Private::HandleOtaUpdateSetUpdateChannelCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 12 | { 13 | auto msg = root->payload_as_OtaUpdateSetUpdateChannelCommand(); 14 | if (msg == nullptr) { 15 | OS_LOGE(TAG, "Payload cannot be parsed as OtaUpdateSetUpdateChannelCommand"); 16 | return; 17 | } 18 | 19 | // TODO 20 | } 21 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/OtaUpdateStartUpdateCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | #include "Logging.h" 4 | 5 | #include 6 | 7 | const char* const TAG = "LocalMessageHandlers"; 8 | 9 | using namespace OpenShock::MessageHandlers::Local; 10 | 11 | void _Private::HandleOtaUpdateStartUpdateCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 12 | { 13 | auto msg = root->payload_as_OtaUpdateStartUpdateCommand(); 14 | if (msg == nullptr) { 15 | OS_LOGE(TAG, "Payload cannot be parsed as OtaUpdateStartUpdateCommand"); 16 | return; 17 | } 18 | 19 | // TODO 20 | } 21 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/SetRfTxPinCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | const char* const TAG = "LocalMessageHandlers"; 4 | 5 | #include "CaptivePortal.h" 6 | #include "CommandHandler.h" 7 | #include "Common.h" 8 | #include "Logging.h" 9 | 10 | #include 11 | 12 | void serializeSetRfTxPinResult(uint8_t socketId, gpio_num_t pin, OpenShock::Serialization::Local::SetGPIOResultCode result) 13 | { 14 | flatbuffers::FlatBufferBuilder builder(1024); 15 | 16 | auto responseOffset = OpenShock::Serialization::Local::CreateSetRfTxPinCommandResult(builder, static_cast(pin), result); 17 | 18 | auto msg = OpenShock::Serialization::Local::CreateHubToLocalMessage(builder, OpenShock::Serialization::Local::HubToLocalMessagePayload::SetRfTxPinCommandResult, responseOffset.Union()); 19 | 20 | OpenShock::Serialization::Local::FinishHubToLocalMessageBuffer(builder, msg); 21 | 22 | const uint8_t* buffer = builder.GetBufferPointer(); 23 | uint8_t size = builder.GetSize(); 24 | 25 | OpenShock::CaptivePortal::SendMessageBIN(socketId, buffer, size); 26 | } 27 | 28 | using namespace OpenShock::MessageHandlers::Local; 29 | 30 | void _Private::HandleSetRfTxPinCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 31 | { 32 | auto msg = root->payload_as_SetRfTxPinCommand(); 33 | if (msg == nullptr) { 34 | OS_LOGE(TAG, "Payload cannot be parsed as SetRfTxPinCommand"); 35 | return; 36 | } 37 | 38 | auto pin = msg->pin(); 39 | 40 | auto result = OpenShock::CommandHandler::SetRfTxPin(static_cast(pin)); 41 | 42 | serializeSetRfTxPinResult(socketId, static_cast(pin), result); 43 | } 44 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/WiFiNetworkConnectCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | const char* const TAG = "LocalMessageHandlers"; 4 | 5 | #include "Logging.h" 6 | #include "util/HexUtils.h" 7 | #include "wifi/WiFiManager.h" 8 | 9 | #include 10 | 11 | using namespace OpenShock::MessageHandlers::Local; 12 | 13 | void _Private::HandleWifiNetworkConnectCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 14 | { 15 | (void)socketId; 16 | 17 | auto msg = root->payload_as_WifiNetworkConnectCommand(); 18 | if (msg == nullptr) { 19 | OS_LOGE(TAG, "Payload cannot be parsed as WiFiNetworkConnectCommand"); 20 | return; 21 | } 22 | 23 | auto ssid = msg->ssid(); 24 | 25 | if (ssid == nullptr) { 26 | OS_LOGE(TAG, "WiFi message is missing required properties"); 27 | return; 28 | } 29 | 30 | if (ssid->size() > 31) { 31 | OS_LOGE(TAG, "WiFi SSID is too long"); 32 | return; 33 | } 34 | 35 | if (!WiFiManager::Connect(ssid->c_str())) { // TODO: support hidden networks 36 | OS_LOGE(TAG, "Failed to connect to WiFi network"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/WiFiNetworkDisconnectCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | const char* const TAG = "LocalMessageHandlers"; 4 | 5 | #include "Logging.h" 6 | #include "util/HexUtils.h" 7 | #include "wifi/WiFiManager.h" 8 | 9 | #include 10 | 11 | using namespace OpenShock::MessageHandlers::Local; 12 | 13 | void _Private::HandleWifiNetworkDisconnectCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 14 | { 15 | (void)socketId; 16 | 17 | auto msg = root->payload_as_WifiNetworkDisconnectCommand(); 18 | if (msg == nullptr) { 19 | OS_LOGE(TAG, "Payload cannot be parsed as WiFiNetworkDisconnectCommand"); 20 | return; 21 | } 22 | 23 | WiFiManager::Disconnect(); 24 | } 25 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/WiFiNetworkForgetCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | const char* const TAG = "LocalMessageHandlers"; 4 | 5 | #include "Logging.h" 6 | #include "util/HexUtils.h" 7 | #include "wifi/WiFiManager.h" 8 | 9 | #include 10 | 11 | using namespace OpenShock::MessageHandlers::Local; 12 | 13 | void _Private::HandleWifiNetworkForgetCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 14 | { 15 | (void)socketId; 16 | 17 | auto msg = root->payload_as_WifiNetworkForgetCommand(); 18 | if (msg == nullptr) { 19 | OS_LOGE(TAG, "Payload cannot be parsed as WiFiNetworkForgetCommand"); 20 | return; 21 | } 22 | 23 | auto ssid = msg->ssid(); 24 | 25 | if (ssid == nullptr) { 26 | OS_LOGE(TAG, "WiFi message is missing required properties"); 27 | return; 28 | } 29 | 30 | if (ssid->size() > 31) { 31 | OS_LOGE(TAG, "WiFi SSID is too long"); 32 | return; 33 | } 34 | 35 | if (!WiFiManager::Forget(ssid->c_str())) { // TODO: support hidden networks 36 | OS_LOGE(TAG, "Failed to forget WiFi network"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/WiFiNetworkSaveCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | const char* const TAG = "LocalMessageHandlers"; 4 | 5 | #include "Logging.h" 6 | #include "util/HexUtils.h" 7 | #include "wifi/WiFiManager.h" 8 | 9 | #include 10 | 11 | using namespace OpenShock::MessageHandlers::Local; 12 | 13 | void _Private::HandleWifiNetworkSaveCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 14 | { 15 | (void)socketId; 16 | 17 | auto msg = root->payload_as_WifiNetworkSaveCommand(); 18 | if (msg == nullptr) { 19 | OS_LOGE(TAG, "Payload cannot be parsed as WiFiNetworkSaveCommand"); 20 | return; 21 | } 22 | 23 | auto ssid = msg->ssid(); 24 | auto password = msg->password() ? msg->password()->str() : ""; 25 | 26 | if (ssid == nullptr) { 27 | OS_LOGE(TAG, "WiFi message is missing SSID"); 28 | return; 29 | } 30 | 31 | if (ssid->size() > 31) { 32 | OS_LOGE(TAG, "WiFi SSID is too long"); 33 | return; 34 | } 35 | 36 | std::size_t passwordLength = password.size(); 37 | 38 | if (passwordLength != 0 && passwordLength < 8) { 39 | OS_LOGE(TAG, "WiFi password is too short"); 40 | return; 41 | } 42 | 43 | if (passwordLength > 63) { 44 | OS_LOGE(TAG, "WiFi password is too long"); 45 | return; 46 | } 47 | 48 | if (!WiFiManager::Save(ssid->c_str(), password)) { 49 | OS_LOGE(TAG, "Failed to save WiFi network"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/WiFiScanCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | const char* const TAG = "LocalMessageHandlers"; 4 | 5 | #include "Logging.h" 6 | #include "wifi/WiFiScanManager.h" 7 | 8 | using namespace OpenShock::MessageHandlers::Local; 9 | 10 | void _Private::HandleWifiScanCommand(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 11 | { 12 | (void)socketId; 13 | 14 | auto msg = root->payload_as_WifiScanCommand(); 15 | if (msg == nullptr) { 16 | OS_LOGE(TAG, "Payload cannot be parsed as WiFiScanCommand"); 17 | return; 18 | } 19 | 20 | if (msg->run()) { 21 | WiFiScanManager::StartScan(); 22 | } else { 23 | WiFiScanManager::AbortScan(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/message_handlers/websocket/local/_InvalidMessage.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handlers/impl/WSLocal.h" 2 | 3 | const char* const TAG = "LocalMessageHandlers"; 4 | 5 | #include "Logging.h" 6 | 7 | using namespace OpenShock::MessageHandlers::Local; 8 | 9 | void _Private::HandleInvalidMessage(uint8_t socketId, const OpenShock::Serialization::Local::LocalToHubMessage* root) 10 | { 11 | (void)socketId; 12 | 13 | if (root == nullptr) { 14 | OS_LOGE(TAG, "Message cannot be parsed"); 15 | return; 16 | } 17 | 18 | OS_LOGE(TAG, "Invalid message type: %d", root->payload_type()); 19 | } 20 | -------------------------------------------------------------------------------- /src/radio/rmt/PetrainerEncoder.cpp: -------------------------------------------------------------------------------- 1 | #include "radio/rmt/PetrainerEncoder.h" 2 | 3 | #include "radio/rmt/internal/Shared.h" 4 | 5 | #include 6 | 7 | const rmt_data_t kRmtPreamble = {750, 1, 750, 0}; 8 | const rmt_data_t kRmtOne = {200, 1, 1500, 0}; 9 | const rmt_data_t kRmtZero = {200, 1, 750, 0}; 10 | const rmt_data_t kRmtPostamble = {200, 1, 7000, 0}; 11 | 12 | using namespace OpenShock; 13 | 14 | size_t Rmt::PetrainerEncoder::GetBufferSize() 15 | { 16 | return 42; 17 | } 18 | 19 | bool Rmt::PetrainerEncoder::FillBuffer(rmt_data_t* sequence, uint16_t shockerId, ShockerCommandType type, uint8_t intensity) 20 | { 21 | // Intensity must be between 0 and 100 22 | intensity = std::min(intensity, static_cast(100)); 23 | 24 | uint8_t nShift = 0; 25 | switch (type) { 26 | case ShockerCommandType::Shock: 27 | nShift = 0; 28 | break; 29 | case ShockerCommandType::Vibrate: 30 | nShift = 1; 31 | break; 32 | case ShockerCommandType::Sound: 33 | nShift = 2; 34 | break; 35 | default: 36 | return false; // Invalid type 37 | } 38 | 39 | // Type is 0x80 | (0x01 << nShift) 40 | uint8_t typeVal = (0x80 | (0x01 << nShift)) & 0xFF; 41 | 42 | // TypeSum is NOT(0x01 | (0x80 >> nShift)) 43 | uint8_t typeSum = (~(0x01 | (0x80 >> nShift))) & 0xFF; 44 | 45 | // Payload layout: [methodBit:8][shockerId:16][intensity:8][methodChecksum:8] 46 | uint64_t data = (static_cast(typeVal) << 32) | (static_cast(shockerId) << 16) | (static_cast(intensity) << 8) | static_cast(typeSum); 47 | 48 | // Generate the sequence 49 | sequence[0] = kRmtPreamble; 50 | Rmt::Internal::EncodeBits<40>(sequence + 1, data, kRmtOne, kRmtZero); 51 | sequence[41] = kRmtPostamble; 52 | 53 | return true; 54 | } 55 | -------------------------------------------------------------------------------- /src/serial/command_handlers/CommandEntry.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/CommandEntry.h" 2 | 3 | using namespace OpenShock::Serial; 4 | 5 | CommandEntry::CommandEntry(std::string_view description, CommandHandler commandHandler) 6 | : m_description(description) 7 | , m_commandHandler(commandHandler) { 8 | } 9 | 10 | CommandEntry::CommandEntry(std::string_view name, std::string_view description, CommandHandler commandHandler) 11 | : m_name(name) 12 | , m_description(description) 13 | , m_commandHandler(commandHandler) { 14 | } 15 | 16 | CommandArgument& CommandEntry::addArgument(std::string_view name, std::string_view constraint, std::string_view exampleValue, std::vector constraintExtensions) { 17 | m_arguments.push_back({name, constraint, exampleValue, constraintExtensions}); 18 | return m_arguments.back(); 19 | } 20 | 21 | CommandGroup::CommandGroup(std::string_view name) 22 | : m_name(name) { 23 | } 24 | 25 | CommandEntry& CommandGroup::addCommand(std::string_view description, CommandHandler commandHandler) { 26 | m_commands.emplace_back(description, commandHandler); 27 | return m_commands.back(); 28 | } 29 | 30 | CommandEntry& CommandGroup::addCommand(std::string_view name, std::string_view description, CommandHandler commandHandler) { 31 | m_commands.emplace_back(name, description, commandHandler); 32 | return m_commands.back(); 33 | } 34 | -------------------------------------------------------------------------------- /src/serial/command_handlers/authtoken.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/common.h" 2 | 3 | #include "config/Config.h" 4 | #include "http/JsonAPI.h" 5 | 6 | #include 7 | 8 | void _handleAuthtokenCommand(std::string_view arg, bool isAutomated) { 9 | if (arg.empty()) { 10 | std::string authToken; 11 | if (!OpenShock::Config::GetBackendAuthToken(authToken)) { 12 | SERPR_ERROR("Failed to get auth token from config"); 13 | return; 14 | } 15 | 16 | // Get auth token 17 | SERPR_RESPONSE("AuthToken|%s", authToken.c_str()); 18 | return; 19 | } 20 | 21 | auto apiResponse = OpenShock::HTTP::JsonAPI::GetHubInfo(arg); 22 | if (apiResponse.code == 401) { 23 | SERPR_ERROR("Invalid auth token, refusing to save it!"); 24 | return; 25 | } 26 | 27 | // If we have some other kind of request fault just set it anyway, we probably arent connected to a network 28 | 29 | bool result = OpenShock::Config::SetBackendAuthToken(arg); 30 | 31 | if (result) { 32 | SERPR_SUCCESS("Saved config"); 33 | } else { 34 | SERPR_ERROR("Failed to save config"); 35 | } 36 | } 37 | 38 | OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::AuthTokenHandler() { 39 | auto group = OpenShock::Serial::CommandGroup("authtoken"sv); 40 | 41 | auto& getCommand = group.addCommand("Get the backend auth token"sv, _handleAuthtokenCommand); 42 | 43 | auto& setCommand = group.addCommand("Set the auth token"sv, _handleAuthtokenCommand); 44 | setCommand.addArgument("token"sv, "must be a string"sv, "mytoken"sv); 45 | 46 | return group; 47 | } 48 | -------------------------------------------------------------------------------- /src/serial/command_handlers/echo.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/common.h" 2 | 3 | #include "serial/SerialInputHandler.h" 4 | 5 | #include "config/Config.h" 6 | #include "Convert.h" 7 | #include "util/StringUtils.h" 8 | 9 | void _handleSerialEchoCommand(std::string_view arg, bool isAutomated) { 10 | if (arg.empty()) { 11 | // Get current serial echo status 12 | SERPR_RESPONSE("SerialEcho|%s", OpenShock::SerialInputHandler::SerialEchoEnabled() ? "true" : "false"); 13 | return; 14 | } 15 | 16 | bool enabled; 17 | if (!OpenShock::Convert::ToBool(OpenShock::StringTrim(arg), enabled)) { 18 | SERPR_ERROR("Invalid argument (not a boolean)"); 19 | return; 20 | } 21 | 22 | bool result = OpenShock::Config::SetSerialInputConfigEchoEnabled(enabled); 23 | OpenShock::SerialInputHandler::SetSerialEchoEnabled(enabled); 24 | 25 | if (result) { 26 | SERPR_SUCCESS("Saved config"); 27 | } else { 28 | SERPR_ERROR("Failed to save config"); 29 | } 30 | } 31 | 32 | OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::EchoHandler() { 33 | auto group = OpenShock::Serial::CommandGroup("echo"sv); 34 | 35 | auto& getCommand = group.addCommand("Get the serial echo status"sv, _handleSerialEchoCommand); 36 | 37 | auto& setCommand = group.addCommand("Enable/disable serial echo"sv, _handleSerialEchoCommand); 38 | setCommand.addArgument("enabled"sv, "must be a boolean"sv, "true"sv); 39 | 40 | return group; 41 | } 42 | -------------------------------------------------------------------------------- /src/serial/command_handlers/factoryreset.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/common.h" 2 | 3 | #include "config/Config.h" 4 | 5 | #include 6 | 7 | void _handleFactoryResetCommand(std::string_view arg, bool isAutomated) 8 | { 9 | (void)arg; 10 | 11 | ::Serial.println("Resetting to factory defaults..."); 12 | OpenShock::Config::FactoryReset(); 13 | ::Serial.println("Restarting..."); 14 | esp_restart(); 15 | } 16 | 17 | OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::FactoryResetHandler() 18 | { 19 | auto group = OpenShock::Serial::CommandGroup("factoryreset"sv); 20 | 21 | auto& cmd = group.addCommand("Reset the hub to factory defaults and restart"sv, _handleFactoryResetCommand); 22 | 23 | return group; 24 | } 25 | -------------------------------------------------------------------------------- /src/serial/command_handlers/hostname.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/common.h" 2 | 3 | #include "config/Config.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | const char* const TAG = "Serial::CommandHandlers::Domain"; 10 | 11 | void _handleHostnameCommand(std::string_view arg, bool isAutomated) { 12 | if (arg.empty()) { 13 | std::string hostname; 14 | if (!OpenShock::Config::GetWiFiHostname(hostname)) { 15 | SERPR_ERROR("Failed to get hostname from config"); 16 | return; 17 | } 18 | // Get hostname 19 | SERPR_RESPONSE("Hostname|%s", hostname.c_str()); 20 | return; 21 | } 22 | 23 | bool result = OpenShock::Config::SetWiFiHostname(arg); 24 | if (result) { 25 | SERPR_SUCCESS("Saved config, restarting..."); 26 | esp_restart(); 27 | } else { 28 | SERPR_ERROR("Failed to save config"); 29 | } 30 | } 31 | 32 | OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::HostnameHandler() { 33 | auto group = OpenShock::Serial::CommandGroup("hostname"sv); 34 | 35 | auto& getCommand = group.addCommand("Get the network hostname."sv, _handleHostnameCommand); 36 | 37 | auto& setCommand = group.addCommand("Set the network hostname."sv, _handleHostnameCommand); 38 | setCommand.addArgument("hostname"sv, "must be a string"sv, "OpenShock"sv); 39 | 40 | return group; 41 | } 42 | -------------------------------------------------------------------------------- /src/serial/command_handlers/jsonconfig.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/common.h" 2 | 3 | #include "config/Config.h" 4 | 5 | #include 6 | 7 | void _handleJsonConfigCommand(std::string_view arg, bool isAutomated) { 8 | if (arg.empty()) { 9 | // Get raw config 10 | std::string json = OpenShock::Config::GetAsJSON(true); 11 | 12 | SERPR_RESPONSE("JsonConfig|%s", json.c_str()); 13 | return; 14 | } 15 | 16 | if (!OpenShock::Config::SaveFromJSON(arg)) { 17 | SERPR_ERROR("Failed to save config"); 18 | return; 19 | } 20 | 21 | SERPR_SUCCESS("Saved config, restarting..."); 22 | 23 | esp_restart(); 24 | } 25 | 26 | OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::JsonConfigHandler() { 27 | auto group = OpenShock::Serial::CommandGroup("jsonconfig"sv); 28 | 29 | auto& getCommand = group.addCommand("Get the configuration as JSON"sv, _handleJsonConfigCommand); 30 | 31 | auto& setCommand = group.addCommand("Set the configuration from JSON, and restart"sv, _handleJsonConfigCommand); 32 | setCommand.addArgument("json"sv, "must be a valid JSON object"sv, "{ ... }"sv); 33 | 34 | return group; 35 | } 36 | -------------------------------------------------------------------------------- /src/serial/command_handlers/keepalive.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/common.h" 2 | 3 | #include "CommandHandler.h" 4 | #include "config/Config.h" 5 | #include "Convert.h" 6 | #include "util/StringUtils.h" 7 | 8 | void _handleKeepAliveCommand(std::string_view arg, bool isAutomated) { 9 | bool keepAliveEnabled; 10 | 11 | if (arg.empty()) { 12 | // Get keep alive status 13 | if (!OpenShock::Config::GetRFConfigKeepAliveEnabled(keepAliveEnabled)) { 14 | SERPR_ERROR("Failed to get keep-alive status from config"); 15 | return; 16 | } 17 | 18 | SERPR_RESPONSE("KeepAlive|%s", keepAliveEnabled ? "true" : "false"); 19 | return; 20 | } 21 | 22 | if (!OpenShock::Convert::ToBool(OpenShock::StringTrim(arg), keepAliveEnabled)) { 23 | SERPR_ERROR("Invalid argument (not a boolean)"); 24 | return; 25 | } 26 | 27 | bool result = OpenShock::CommandHandler::SetKeepAliveEnabled(keepAliveEnabled); 28 | 29 | if (result) { 30 | SERPR_SUCCESS("Saved config"); 31 | } else { 32 | SERPR_ERROR("Failed to save config"); 33 | } 34 | } 35 | 36 | OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::KeepAliveHandler() { 37 | auto group = OpenShock::Serial::CommandGroup("keepalive"sv); 38 | 39 | auto& getCommand = group.addCommand("Get the shocker keep-alive status"sv, _handleKeepAliveCommand); 40 | 41 | auto& setCommand = group.addCommand("Enable/disable shocker keep-alive"sv, _handleKeepAliveCommand); 42 | setCommand.addArgument("enabled"sv, "must be a boolean"sv, "true"sv); 43 | 44 | return group; 45 | } 46 | -------------------------------------------------------------------------------- /src/serial/command_handlers/rawconfig.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/common.h" 2 | 3 | #include "config/Config.h" 4 | #include "util/Base64Utils.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | void _handleRawConfigCommand(std::string_view arg, bool isAutomated) { 11 | if (arg.empty()) { 12 | std::vector buffer; 13 | 14 | // Get raw config 15 | if (!OpenShock::Config::GetRaw(buffer)) { 16 | SERPR_ERROR("Failed to get raw config"); 17 | return; 18 | } 19 | 20 | std::string base64; 21 | if (!OpenShock::Base64Utils::Encode(buffer.data(), buffer.size(), base64)) { 22 | SERPR_ERROR("Failed to encode raw config to base64"); 23 | return; 24 | } 25 | 26 | SERPR_RESPONSE("RawConfig|%s", base64.c_str()); 27 | return; 28 | } 29 | 30 | std::vector buffer; 31 | if (!OpenShock::Base64Utils::Decode(arg.data(), arg.length(), buffer)) { 32 | SERPR_ERROR("Failed to decode base64"); 33 | return; 34 | } 35 | 36 | if (!OpenShock::Config::SetRaw(buffer.data(), buffer.size())) { 37 | SERPR_ERROR("Failed to save config"); 38 | return; 39 | } 40 | 41 | SERPR_SUCCESS("Saved config, restarting..."); 42 | 43 | esp_restart(); 44 | } 45 | 46 | OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::RawConfigHandler() { 47 | auto group = OpenShock::Serial::CommandGroup("rawconfig"sv); 48 | 49 | auto& getCommand = group.addCommand("Get the raw binary config"sv, _handleRawConfigCommand); 50 | 51 | auto& setCommand = group.addCommand("Set the raw binary config, and restart"sv, _handleRawConfigCommand); 52 | setCommand.addArgument("base64"sv, "must be a base64 encoded string"sv, "(base64 encoded binary data)"sv); 53 | 54 | return group; 55 | } 56 | -------------------------------------------------------------------------------- /src/serial/command_handlers/restart.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/common.h" 2 | 3 | #include 4 | 5 | void _handleRestartCommand(std::string_view arg, bool isAutomated) { 6 | (void)arg; 7 | 8 | ::Serial.println("Restarting ESP..."); 9 | esp_restart(); 10 | } 11 | 12 | OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::RestartHandler() { 13 | auto group = OpenShock::Serial::CommandGroup("restart"sv); 14 | 15 | auto& cmd = group.addCommand("Restart the board"sv, _handleRestartCommand); 16 | 17 | return group; 18 | } 19 | -------------------------------------------------------------------------------- /src/serial/command_handlers/rftxpin.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/common.h" 2 | 3 | #include "CommandHandler.h" 4 | #include "config/Config.h" 5 | #include "Convert.h" 6 | #include "SetGPIOResultCode.h" 7 | 8 | void _handleRfTxPinCommand(std::string_view arg, bool isAutomated) 9 | { 10 | gpio_num_t txPin; 11 | 12 | if (arg.empty()) { 13 | if (!OpenShock::Config::GetRFConfigTxPin(txPin)) { 14 | SERPR_ERROR("Failed to get RF TX pin from config"); 15 | return; 16 | } 17 | 18 | // Get rmt pin 19 | SERPR_RESPONSE("RmtPin|%hhi", static_cast(txPin)); 20 | return; 21 | } 22 | 23 | if (!OpenShock::Convert::ToGpioNum(arg, txPin)) { 24 | SERPR_ERROR("Invalid argument (number invalid or out of range)"); 25 | } 26 | 27 | OpenShock::SetGPIOResultCode result = OpenShock::CommandHandler::SetRfTxPin(txPin); 28 | 29 | switch (result) { 30 | case OpenShock::SetGPIOResultCode::InvalidPin: 31 | SERPR_ERROR("Invalid argument (invalid pin)"); 32 | break; 33 | 34 | case OpenShock::SetGPIOResultCode::InternalError: 35 | SERPR_ERROR("Internal error while setting RF TX pin"); 36 | break; 37 | 38 | case OpenShock::SetGPIOResultCode::Success: 39 | SERPR_SUCCESS("Saved config"); 40 | break; 41 | 42 | default: 43 | SERPR_ERROR("Unknown error while setting RF TX pin"); 44 | break; 45 | } 46 | } 47 | 48 | OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::RfTxPinHandler() 49 | { 50 | auto group = OpenShock::Serial::CommandGroup("rftxpin"sv); 51 | 52 | auto& getCommand = group.addCommand("Get the GPIO pin used for the radio transmitter"sv, _handleRfTxPinCommand); 53 | 54 | auto& setCommand = group.addCommand("Set the GPIO pin used for the radio transmitter"sv, _handleRfTxPinCommand); 55 | setCommand.addArgument("pin"sv, "must be a number"sv, "15"sv); 56 | 57 | return group; 58 | } 59 | -------------------------------------------------------------------------------- /src/serial/command_handlers/sysinfo.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/common.h" 2 | 3 | #include "Core.h" 4 | #include "FormatHelpers.h" 5 | #include "wifi/WiFiManager.h" 6 | #include "wifi/WiFiNetwork.h" 7 | 8 | void _handleDebugInfoCommand(std::string_view arg, bool isAutomated) { 9 | (void)arg; 10 | 11 | SERPR_RESPONSE("RTOSInfo|Free Heap|%u", xPortGetFreeHeapSize()); 12 | SERPR_RESPONSE("RTOSInfo|Min Free Heap|%u", xPortGetMinimumEverFreeHeapSize()); 13 | 14 | const int64_t now = OpenShock::millis(); 15 | SERPR_RESPONSE("RTOSInfo|UptimeMS|%lli", now); 16 | 17 | const int64_t seconds = now / 1000; 18 | const int64_t minutes = seconds / 60; 19 | const int64_t hours = minutes / 60; 20 | const int64_t days = hours / 24; 21 | SERPR_RESPONSE("RTOSInfo|Uptime|%llid %llih %llim %llis", days, hours % 24, minutes % 60, seconds % 60); 22 | 23 | OpenShock::WiFiNetwork network; 24 | bool connected = OpenShock::WiFiManager::GetConnectedNetwork(network); 25 | SERPR_RESPONSE("WiFiInfo|Connected|%s", connected ? "true" : "false"); 26 | if (connected) { 27 | SERPR_RESPONSE("WiFiInfo|SSID|%s", network.ssid); 28 | SERPR_RESPONSE("WiFiInfo|BSSID|" BSSID_FMT, BSSID_ARG(network.bssid)); 29 | 30 | char ipAddressBuffer[64]; 31 | OpenShock::WiFiManager::GetIPAddress(ipAddressBuffer); 32 | SERPR_RESPONSE("WiFiInfo|IPv4|%s", ipAddressBuffer); 33 | OpenShock::WiFiManager::GetIPv6Address(ipAddressBuffer); 34 | SERPR_RESPONSE("WiFiInfo|IPv6|%s", ipAddressBuffer); 35 | } 36 | } 37 | 38 | OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::SysInfoHandler() { 39 | auto group = OpenShock::Serial::CommandGroup("sysinfo"sv); 40 | 41 | auto& cmd = group.addCommand("Get system information from RTOS, WiFi, etc."sv, _handleDebugInfoCommand); 42 | 43 | return group; 44 | } 45 | -------------------------------------------------------------------------------- /src/serial/command_handlers/validgpios.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/common.h" 2 | 3 | #include "Chipset.h" 4 | 5 | #include 6 | 7 | void _handleValidGpiosCommand(std::string_view arg, bool isAutomated) { 8 | if (!arg.empty()) { 9 | SERPR_ERROR("Invalid argument (too many arguments)"); 10 | return; 11 | } 12 | 13 | auto pins = OpenShock::GetValidGPIOPins(); 14 | 15 | std::string buffer; 16 | buffer.reserve(pins.count() * 4); 17 | 18 | for (std::size_t i = 0; i < pins.size(); i++) { 19 | if (pins[i]) { 20 | buffer.append(std::to_string(i)); 21 | buffer.append(","); 22 | } 23 | } 24 | 25 | if (!buffer.empty()) { 26 | buffer.pop_back(); 27 | } 28 | 29 | SERPR_RESPONSE("ValidGPIOs|%s", buffer.c_str()); 30 | } 31 | 32 | OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::ValidGpiosHandler() { 33 | auto group = OpenShock::Serial::CommandGroup("validgpios"sv); 34 | 35 | auto& cmd = group.addCommand("List all valid GPIO pins"sv, _handleValidGpiosCommand); 36 | 37 | return group; 38 | } 39 | -------------------------------------------------------------------------------- /src/serial/command_handlers/version.cpp: -------------------------------------------------------------------------------- 1 | #include "serial/command_handlers/common.h" 2 | 3 | #include "serial/SerialInputHandler.h" 4 | 5 | #include 6 | 7 | void _handleVersionCommand(std::string_view arg, bool isAutomated) { 8 | (void)arg; 9 | 10 | ::Serial.println(); 11 | OpenShock::SerialInputHandler::PrintVersionInfo(); 12 | } 13 | 14 | OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::VersionHandler() { 15 | auto group = OpenShock::Serial::CommandGroup("version"sv); 16 | 17 | auto cmd = group.addCommand("Print version information"sv, _handleVersionCommand); 18 | 19 | return group; 20 | } 21 | -------------------------------------------------------------------------------- /src/util/CertificateUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "util/CertificateUtils.h" 2 | 3 | const char* const TAG = "CertificateUtils"; 4 | 5 | #include "Logging.h" 6 | 7 | #include 8 | 9 | const char* const PEM_HEADER = "-----BEGIN CERTIFICATE-----\n"; 10 | const char* const PEM_FOOTER = "-----END CERTIFICATE-----\n"; 11 | 12 | extern const uint8_t* const rootca_crt_bundle_start asm("_binary_certificates_x509_crt_bundle_start"); 13 | 14 | WiFiClientSecure OpenShock::CertificateUtils::GetSecureClient() { 15 | WiFiClientSecure client; 16 | 17 | client.setCACertBundle(rootca_crt_bundle_start); 18 | 19 | return client; 20 | } 21 | 22 | bool OpenShock::CertificateUtils::GetHostCertificate(const char* host, std::vector& pem) { 23 | WiFiClientSecure client = GetSecureClient(); 24 | 25 | client.connect(host, 443); 26 | 27 | if (client.connected() == 0) { 28 | OS_LOGE(TAG, "Failed to connect to host %s", host); 29 | return false; 30 | } 31 | 32 | OS_LOGD(TAG, "Connected to host %s, fetching certificate", host); 33 | 34 | const mbedtls_x509_crt* cert = client.getPeerCertificate(); 35 | 36 | if (cert == nullptr) { 37 | OS_LOGE(TAG, "Certificate is null"); 38 | return false; 39 | } 40 | 41 | const mbedtls_x509_buf& der = cert->raw; 42 | 43 | uint8_t c; 44 | std::size_t pemLen; 45 | mbedtls_pem_write_buffer(PEM_HEADER, PEM_FOOTER, der.p, der.len, &c, 1, &pemLen); 46 | 47 | pem.resize(pemLen); 48 | int retval = mbedtls_pem_write_buffer(PEM_HEADER, PEM_FOOTER, der.p, der.len, reinterpret_cast(pem.data()), pem.size(), nullptr); 49 | if (retval != 0) { 50 | OS_LOGE(TAG, "Failed to write pem buffer: %d", retval); 51 | return false; 52 | } 53 | 54 | OS_LOGD(TAG, "Successfully fetched certificate from host %s", host); 55 | 56 | return true; 57 | } 58 | -------------------------------------------------------------------------------- /src/util/DigitCounter.cpp: -------------------------------------------------------------------------------- 1 | #include "util/DigitCounter.h" 2 | 3 | using namespace OpenShock; 4 | 5 | static_assert(Util::Digits10CountMax == 3, "NumDigits test for uint8_t failed"); 6 | static_assert(Util::Digits10CountMax == 5, "NumDigits test for uint16_t failed"); 7 | static_assert(Util::Digits10CountMax == 10, "NumDigits test for uint32_t failed"); 8 | static_assert(Util::Digits10CountMax == 20, "NumDigits test for uint64_t failed"); 9 | 10 | static_assert(Util::Digits10CountMax == 4, "NumDigits test for int8_t failed"); 11 | static_assert(Util::Digits10CountMax == 6, "NumDigits test for int16_t failed"); 12 | static_assert(Util::Digits10CountMax == 11, "NumDigits test for int32_t failed"); 13 | static_assert(Util::Digits10CountMax == 20, "NumDigits test for int64_t failed"); 14 | -------------------------------------------------------------------------------- /src/util/IPAddressUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "util/IPAddressUtils.h" 2 | 3 | #include "Convert.h" 4 | #include "util/StringUtils.h" 5 | 6 | const char* const TAG = "Util::IPAddressUtils"; 7 | 8 | bool OpenShock::IPV4AddressFromStringView(IPAddress& ip, std::string_view sv) { 9 | if (sv.empty()) { 10 | return false; 11 | } 12 | 13 | std::string_view parts[4]; 14 | if (!OpenShock::TryStringSplit(sv, '.', parts)) { 15 | return false; // Must have 4 octets 16 | } 17 | 18 | std::uint8_t octets[4]; 19 | if (!Convert::ToUint8(parts[0], octets[0]) || !Convert::ToUint8(parts[1], octets[1]) || !Convert::ToUint8(parts[2], octets[2]) || !Convert::ToUint8(parts[3], octets[3])) { 20 | return false; 21 | } 22 | 23 | ip = IPAddress(octets); 24 | 25 | return true; 26 | } 27 | -------------------------------------------------------------------------------- /src/util/TaskUtils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "util/TaskUtils.h" 4 | 5 | using namespace OpenShock; 6 | 7 | /// @brief Create a task on the specified core, or the default core if the specified core is invalid 8 | BaseType_t TaskUtils::TaskCreateUniversal(TaskFunction_t pvTaskCode, const char* const pcName, const uint32_t usStackDepth, void* const pvParameters, UBaseType_t uxPriority, TaskHandle_t* const pvCreatedTask, const BaseType_t xCoreID) 9 | { 10 | #ifndef CONFIG_FREERTOS_UNICORE 11 | if (xCoreID >= 0 && xCoreID < portNUM_PROCESSORS) { 12 | return xTaskCreatePinnedToCore(pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pvCreatedTask, xCoreID); 13 | } 14 | #endif 15 | return xTaskCreate(pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pvCreatedTask); 16 | } 17 | 18 | /// @brief Create a task on the core that does expensive work, this should not run on the core that handles WiFi 19 | BaseType_t TaskUtils::TaskCreateExpensive(TaskFunction_t pvTaskCode, const char* const pcName, const uint32_t usStackDepth, void* const pvParameters, UBaseType_t uxPriority, TaskHandle_t* const pvCreatedTask) 20 | { 21 | #if portNUM_PROCESSORS > 2 22 | #warning "This chip has more than 2 cores. Please update this code to use the correct core." 23 | #endif 24 | // Run on core 1 (0 handles WiFi and should be minimally used) 25 | return TaskCreateUniversal(pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pvCreatedTask, 1); 26 | } 27 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | --------------------------------------------------------------------------------