├── .github └── workflows │ └── release.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── LICENSE ├── README.md ├── auto_firmware_version.py ├── build_flags.py ├── build_web_content.py ├── docs ├── README.md ├── build_size.log ├── discord-logo.png ├── firmware │ ├── homekit-ratgdo-v0.1.0.bin │ ├── homekit-ratgdo-v0.10.0.bin │ ├── homekit-ratgdo-v0.11.0.bin │ ├── homekit-ratgdo-v0.12.0.bin │ ├── homekit-ratgdo-v0.2.0.bin │ ├── homekit-ratgdo-v0.2.1.bin │ ├── homekit-ratgdo-v0.2.2.bin │ ├── homekit-ratgdo-v0.2.3.bin │ ├── homekit-ratgdo-v0.3.0.bin │ ├── homekit-ratgdo-v0.3.1.bin │ ├── homekit-ratgdo-v0.4.0.bin │ ├── homekit-ratgdo-v0.5.0.bin │ ├── homekit-ratgdo-v0.6.0.bin │ ├── homekit-ratgdo-v0.7.0.bin │ ├── homekit-ratgdo-v0.8.0.bin │ ├── homekit-ratgdo-v0.9.0.bin │ ├── homekit-ratgdo-v1.0.0.bin │ ├── homekit-ratgdo-v1.1.0.bin │ ├── homekit-ratgdo-v1.2.0.bin │ ├── homekit-ratgdo-v1.2.0.elf │ ├── homekit-ratgdo-v1.2.1.bin │ ├── homekit-ratgdo-v1.2.1.elf │ ├── homekit-ratgdo-v1.3.0.bin │ ├── homekit-ratgdo-v1.3.0.elf │ ├── homekit-ratgdo-v1.3.0.md5 │ ├── homekit-ratgdo-v1.3.1.bin │ ├── homekit-ratgdo-v1.3.1.elf │ ├── homekit-ratgdo-v1.3.1.md5 │ ├── homekit-ratgdo-v1.3.2.bin │ ├── homekit-ratgdo-v1.3.2.elf │ ├── homekit-ratgdo-v1.3.2.md5 │ ├── homekit-ratgdo-v1.3.3.bin │ ├── homekit-ratgdo-v1.3.3.elf │ ├── homekit-ratgdo-v1.3.3.md5 │ ├── homekit-ratgdo-v1.3.4.bin │ ├── homekit-ratgdo-v1.3.4.elf │ ├── homekit-ratgdo-v1.3.4.md5 │ ├── homekit-ratgdo-v1.3.5.bin │ ├── homekit-ratgdo-v1.3.5.elf │ ├── homekit-ratgdo-v1.3.5.md5 │ ├── homekit-ratgdo-v1.4.0.bin │ ├── homekit-ratgdo-v1.4.0.elf │ ├── homekit-ratgdo-v1.4.0.md5 │ ├── homekit-ratgdo-v1.4.1.bin │ ├── homekit-ratgdo-v1.4.1.elf │ ├── homekit-ratgdo-v1.4.1.md5 │ ├── homekit-ratgdo-v1.4.2.bin │ ├── homekit-ratgdo-v1.4.2.elf │ ├── homekit-ratgdo-v1.4.2.md5 │ ├── homekit-ratgdo-v1.4.3.bin │ ├── homekit-ratgdo-v1.4.3.elf │ ├── homekit-ratgdo-v1.4.3.md5 │ ├── homekit-ratgdo-v1.4.4.bin │ ├── homekit-ratgdo-v1.4.4.elf │ ├── homekit-ratgdo-v1.4.4.md5 │ ├── homekit-ratgdo-v1.5.0.bin │ ├── homekit-ratgdo-v1.5.0.elf │ ├── homekit-ratgdo-v1.5.0.md5 │ ├── homekit-ratgdo-v1.6.0.bin │ ├── homekit-ratgdo-v1.6.0.elf │ ├── homekit-ratgdo-v1.6.0.md5 │ ├── homekit-ratgdo-v1.6.1.bin │ ├── homekit-ratgdo-v1.6.1.elf │ ├── homekit-ratgdo-v1.6.1.md5 │ ├── homekit-ratgdo-v1.7.0.bin │ ├── homekit-ratgdo-v1.7.0.elf │ ├── homekit-ratgdo-v1.7.0.md5 │ ├── homekit-ratgdo-v1.7.1.bin │ ├── homekit-ratgdo-v1.7.1.elf │ ├── homekit-ratgdo-v1.7.1.md5 │ ├── homekit-ratgdo-v1.8.0.bin │ ├── homekit-ratgdo-v1.8.0.elf │ ├── homekit-ratgdo-v1.8.0.md5 │ ├── homekit-ratgdo-v1.8.1.bin │ ├── homekit-ratgdo-v1.8.1.elf │ ├── homekit-ratgdo-v1.8.1.md5 │ ├── homekit-ratgdo-v1.8.2.bin │ ├── homekit-ratgdo-v1.8.2.elf │ ├── homekit-ratgdo-v1.8.2.md5 │ ├── homekit-ratgdo-v1.8.3.bin │ ├── homekit-ratgdo-v1.8.3.elf │ ├── homekit-ratgdo-v1.8.3.md5 │ ├── homekit-ratgdo-v1.8.4.bin │ ├── homekit-ratgdo-v1.8.4.elf │ └── homekit-ratgdo-v1.8.4.md5 ├── flash.html ├── index.html ├── logs │ ├── README.md │ └── motion.log ├── manifest.json ├── ota │ ├── firmware.png │ ├── ota.png │ ├── updatecrc.png │ ├── updatefail.png │ └── uploaded.png ├── qr.png ├── releasing.md ├── syncing.md └── webpage │ ├── password.png │ ├── rebootcrc.png │ ├── settings.png │ └── webpage.png ├── improv.py ├── lib ├── lwip2 │ ├── README.md │ └── liblwip2-536.a └── ratgdo │ ├── Packet.h │ ├── Reader.h │ ├── log.h │ └── secplus2.h ├── platformio.ini ├── reboot.sh ├── src ├── comms.cpp ├── comms.h ├── homekit.cpp ├── homekit.h ├── homekit_decl.c ├── homekit_decl.h ├── log.cpp ├── ratgdo.cpp ├── ratgdo.h ├── utilities.cpp ├── utilities.h ├── web.cpp ├── web.h ├── wifi.cpp ├── wifi.h └── www │ ├── apple-touch-icon.png │ ├── auth │ ├── favicon.png │ ├── functions.js │ ├── garage-car.svg │ ├── index.html │ ├── logs.html │ ├── logs.js │ ├── qrcode.svg │ ├── settings-sliders.svg │ ├── status.json │ └── style.css ├── test ├── test_packet │ ├── secplus.c │ └── test_main.cpp └── test_reader │ ├── secplus.c │ └── test_main.cpp ├── upload_firmware.sh ├── util.py ├── verify_firmware.sh ├── viewlog.sh └── x.sh /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | actions: write 9 | 10 | jobs: 11 | release: 12 | permissions: 13 | contents: write 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | with: 20 | submodules: true 21 | 22 | - name: Tag 23 | id: tag 24 | uses: JinoArch/get-latest-tag@latest 25 | 26 | - name: Update version on manifest.json 27 | uses: amochkin/action-json@v1 28 | id: write_version 29 | with: 30 | mode: write 31 | file: docs/manifest.json 32 | property: version 33 | value: ${{ steps.tag.outputs.latestTag }} 34 | value_type: string 35 | 36 | - name: Output created (or overwritten) manifest.json 37 | run: cat docs/manifest.json 38 | shell: bash 39 | 40 | - name: Output read value of 'version' property 41 | run: echo ${{ steps.write_version.outputs.value }} 42 | shell: bash 43 | 44 | - name: Update firmware/*.bin version on manifest.json 45 | uses: amochkin/action-json@v1 46 | id: builds_parts_path 47 | with: 48 | mode: write 49 | file: docs/manifest.json 50 | property: builds.0.parts.0.path 51 | value: firmware/homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.bin 52 | value_type: string 53 | 54 | - name: Output created (or overwritten) manifest.json 55 | run: cat docs/manifest.json 56 | shell: bash 57 | 58 | - name: Output read value of 'version' property 59 | run: echo ${{ steps.builds_parts_path.outputs.value }} 60 | shell: bash 61 | 62 | - name: Attach manifest.json 63 | uses: AButler/upload-release-assets@v3.0 64 | with: 65 | files: "/home/runner/work/homekit-ratgdo/homekit-ratgdo/docs/manifest.json" 66 | repo-token: ${{ secrets.GITHUB_TOKEN }} 67 | release-tag: ${{ steps.tag.outputs.latestTag }} 68 | 69 | - name: Upload Release manifest.json 70 | uses: wow-actions/download-upload@v1 71 | with: 72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | url: https://github.com/ratgdo/homekit-ratgdo/releases/download/${{ steps.tag.outputs.latestTag }}/manifest.json 74 | dir: docs/ 75 | commit_message: "Upload Latest manifest.json for ${{ steps.tag.outputs.latestTag }}" 76 | 77 | - uses: actions/cache@v3 78 | with: 79 | path: | 80 | ~/.cache/pip 81 | ~/.platformio/.cache 82 | key: ${{ runner.os }}-pio 83 | - uses: actions/setup-python@v4 84 | with: 85 | python-version: '3.9' 86 | - name: Install PlatformIO Core 87 | run: | 88 | pip install --upgrade pip 89 | pip install --upgrade platformio 90 | 91 | - name: Build PlatformIO Project 92 | run: pio run -e ratgdo_esp8266_hV25 93 | 94 | - name : md5sum Firmware.bin 95 | run: | 96 | cd .pio/build/ratgdo_esp8266_hV25 97 | md5sum firmware.bin | awk '{print $1}' > firmware.md5 98 | 99 | - name: Rename Firmware Files 100 | run: | 101 | mv .pio/build/ratgdo_esp8266_hV25/firmware.bin .pio/build/ratgdo_esp8266_hV25/homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.bin 102 | mv .pio/build/ratgdo_esp8266_hV25/firmware.elf .pio/build/ratgdo_esp8266_hV25/homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.elf 103 | mv .pio/build/ratgdo_esp8266_hV25/firmware.md5 .pio/build/ratgdo_esp8266_hV25/homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.md5 104 | 105 | - name: Attach Bundle - Firmware.bin 106 | uses: AButler/upload-release-assets@v3.0 107 | with: 108 | files: ".pio/build/ratgdo_esp8266_hV25/*.bin" 109 | repo-token: ${{ secrets.GITHUB_TOKEN }} 110 | release-tag: ${{ steps.tag.outputs.latestTag }} 111 | 112 | - name: Upload Release Asset - Firmware.bin 113 | uses: wow-actions/download-upload@v1 114 | with: 115 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 116 | url: https://github.com/ratgdo/homekit-ratgdo/releases/download/${{ steps.tag.outputs.latestTag }}/homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.bin 117 | dir: docs/firmware/ 118 | commit_message: "Upload Latest Firmware: homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.bin" 119 | 120 | - name: Upload Firmware.bin 121 | uses: actions/upload-artifact@v4 122 | with: 123 | name: homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.bin 124 | path: | 125 | .pio/build/ratgdo_esp8266_hV25/*.bin 126 | 127 | - name: Download Firmware.bin 128 | uses: actions/download-artifact@v4 129 | with: 130 | name: homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.bin 131 | path: | 132 | docs/firmware/ 133 | 134 | - name: Attach Bundle - Firmware.md5 135 | uses: AButler/upload-release-assets@v3.0 136 | with: 137 | files: ".pio/build/ratgdo_esp8266_hV25/*.md5" 138 | repo-token: ${{ secrets.GITHUB_TOKEN }} 139 | release-tag: ${{ steps.tag.outputs.latestTag }} 140 | 141 | - name: Upload Release Asset - Firmware.md5 142 | uses: wow-actions/download-upload@v1 143 | with: 144 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 145 | url: https://github.com/ratgdo/homekit-ratgdo/releases/download/${{ steps.tag.outputs.latestTag }}/homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.md5 146 | dir: docs/firmware/ 147 | commit_message: "Upload Latest md5: homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.md5" 148 | 149 | - name: Upload Firmware.bin 150 | uses: actions/upload-artifact@v4 151 | with: 152 | name: homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.md5 153 | path: | 154 | .pio/build/ratgdo_esp8266_hV25/*.md5 155 | 156 | - name: Download Firmware.md5 157 | uses: actions/download-artifact@v4 158 | with: 159 | name: homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.md5 160 | path: | 161 | docs/firmware/ 162 | 163 | - name: Attach Bundle - Firmware.elf 164 | uses: AButler/upload-release-assets@v3.0 165 | with: 166 | files: ".pio/build/ratgdo_esp8266_hV25/*.elf" 167 | repo-token: ${{ secrets.GITHUB_TOKEN }} 168 | release-tag: ${{ steps.tag.outputs.latestTag }} 169 | 170 | - name: Upload Release Asset - Firmware.elf 171 | uses: wow-actions/download-upload@v1 172 | with: 173 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 174 | url: https://github.com/ratgdo/homekit-ratgdo/releases/download/${{ steps.tag.outputs.latestTag }}/homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.elf 175 | dir: docs/firmware/ 176 | commit_message: "Upload Latest Firmware: homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.elf" 177 | 178 | - name: Upload Firmware.elf 179 | uses: actions/upload-artifact@v4 180 | with: 181 | name: homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.elf 182 | path: | 183 | .pio/build/ratgdo_esp8266_hV25/*.elf 184 | 185 | - name: Download Firmware.elf 186 | uses: actions/download-artifact@v4 187 | with: 188 | name: homekit-ratgdo-${{ steps.tag.outputs.latestTag }}.elf 189 | path: | 190 | docs/firmware/ 191 | 192 | - name: Sleep for 2 minutes before pubhsing to Discord 193 | run: sleep 120s 194 | shell: bash 195 | 196 | - name: Latest Release 197 | if: ${{ github.event.release.prerelease == false }} 198 | uses: SethCohen/github-releases-to-discord@v1.13.1 199 | with: 200 | webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }} 201 | color: "5723991" 202 | username: "ratgdo" 203 | avatar_url: "https://avatars.githubusercontent.com/u/144837877?s=200&v=4" 204 | footer_title: "homekit-ratgdo" 205 | footer_icon_url: "https://avatars.githubusercontent.com/u/144837877?s=200&v=4" 206 | footer_timestamp: true 207 | 208 | - name: Pre-Release 209 | if: ${{ github.event.release.prerelease == true }} 210 | uses: SethCohen/github-releases-to-discord@v1.13.1 211 | with: 212 | webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }} 213 | color: "5723991" 214 | username: "ratgdo" 215 | avatar_url: "https://avatars.githubusercontent.com/u/144837877?s=200&v=4" 216 | footer_title: "Pre-Release: homekit-ratgdo" 217 | footer_icon_url: "https://avatars.githubusercontent.com/u/144837877?s=200&v=4" 218 | footer_timestamp: true 219 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ccls 2 | .cache 3 | .ccls-cache 4 | .clang_complete 5 | .gcc-flags.json 6 | .pio 7 | .pioenvs 8 | .piolibdeps 9 | compile_commands.json 10 | __pycache__ 11 | .DS_Store 12 | .vscode 13 | src/www/build 14 | .vscode/* 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "secplus"] 2 | path = lib/secplus 3 | url = https://github.com/argilo/secplus.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to `homekit-ratgdo` will be documented in this file. This project tries to adhere to [Semantic Versioning](http://semver.org/). 4 | 5 | ## v1.8.3 (2024-11-23) 6 | 7 | ### What's Changed 8 | 9 | * Bugfix... Unrecoverable crash/reboot loop for Sec+ reported in . 10 | 11 | ### Known Issues 12 | 13 | * None 14 | 15 | ## v1.8.2 (2024-11-22) 16 | 17 | ### What's Changed 18 | 19 | * New feature... Allow disabling of LED activity. 20 | * New feature... Allow setting syslog server port. 21 | * Bugfix... Passwords do not match message had been removed from html by mistake. 22 | 23 | ### Known Issues 24 | 25 | * None 26 | 27 | ## v1.8.1 (2024-10-29) 28 | 29 | ### What's Changed 30 | 31 | * New feature... Allow selection of time zone when NTP server enabled. 32 | * Change... We use built in Arduino core NTP client for time instead of separate module 33 | * Change... Replace DNS lookup with a ping to gateway to test for network connectivity 34 | * Bugfix... Don't display that an update is available if running newer pre-release 35 | * Bugfix... When changing SSID in soft access point mode, make sure the WiFi settings are reset to DHCP 36 | * Bugfix... wifiSettingsChanged setting had been removed by mistake, impacting recovery from failure to connect. 37 | 38 | ### Known Issues 39 | 40 | * Same as v1.8.0... 41 | * Occasional failure to connect to WiFi. Tracked in 42 | 43 | ## v1.8.0 (2024-10-26) 44 | 45 | This release has significant updates. Please review the [README](https://github.com/ratgdo/homekit-ratgdo/blob/main/README.md) for full documentation of the new features. 46 | 47 | ### What's Changed 48 | 49 | * New feature... add soft AP mode to allow setting WiFi SSID (). 50 | * New feature... enable IRAM heap to increase available memory, expected to improve reliability. 51 | * New feature... move all user config settings into single file, improves boot time by ~13 seconds (). 52 | * New feature... add support for logging to a syslog server. 53 | * Change... Reboot countdown timer changed from 30 seconds to 15 seconds 54 | * Change... Remove option to receive server logs to JavaScript console, replaced by syslog and system logs page. 55 | * Bugfix... ensure that network hostname is RFC952 compliant (e.g. no spaces). 56 | * Bugfix... Possible fix to by modifying HomeKit server malloc() from local to global. 57 | * Bugfix... Possible fix to and as we enable IRAM heap. 58 | * Bugfix... Occasional HomeKit notification that garage door is unlocked. Tracked in 59 | 60 | ### Known Issues 61 | 62 | * Occasional failure to connect to WiFi. Tracked in 63 | 64 | ## v1.7.1 (2024-09-23) 65 | 66 | ### What's Changed 67 | 68 | * Bugfix... alignment of minHeap and minStack was wrong on chrome browsers. 69 | * Bugfix... set WiFi hostname based on user provided device name. 70 | * Bugfix... also set the browser page title to the user provided device name. 71 | * New feature... allow user to set custom username for the webpage. 72 | * New feature... allow user to set static IP address. 73 | * New feature... add new system logs page (opens in new browser tab). 74 | * New feature... add spinning page-loading icon to provide feedback on slow network links 75 | * New feature... add option to obtain real time from NTP sever 76 | * Cleanup... consolidated all code for retrieving user configured settings into one file (utilities.cpp). 77 | 78 | ### Known Issues 79 | 80 | * Same as v1.7.0 81 | 82 | ## v1.7.0 (2024-08-10) 83 | 84 | ### What's Changed 85 | 86 | * Removed the heap fragmentation tracking introduced in v1.6.1... it was occasionally crashing inside Arduino library. 87 | * Bugfix... device reboot countdown timer from 30 seconds to zero was not always displaying. 88 | * New feature... add _reset door_ button. This resets the Sec+ 2.0 rolling code and whether the door has a motion sensor or not. 89 | * New feature... add option to trigger motion sensor on user pressing wall panel buttons (door open/close, light and/or lock). 90 | * New feature... add option to trigger motion sensor on door obstruction. 91 | * New feature... add option for user to select whether the LED light remains on while device is idle or turns off. Any activity causes the LED to flash. 92 | 93 | ### Known Issues 94 | 95 | * Random crashes inside the MDNSresponder code which usually occur shortly after booting or when something significant changes on the network that causes a storm of mDNS messages. Ratgdo always recovers. Tracked in , 96 | * Random crashes inside the WiFi stack possibly associated with failing to connect to WiFi access point. Tracked in , , 97 | * Occasional failure to connect to WiFi. Tracked in 98 | 99 | ## v1.6.1 (2024-07-28) 100 | 101 | ### What's Changed 102 | 103 | * Fixes to SEC1.0 when no DIGITAL wall panel connected by @mitchjs in 104 | * Improve memory heap usage tracking and logging to assist with future debugging 105 | * Cleaned up numerous compiler warnings 106 | 107 | The only functional change in this release is to better support Sec+ 1.0 garage door openers when there is no digital wall panel. Upgrading is therefore optional if your GDO is Sec+ 2.0. 108 | 109 | ## v1.6.0 (2024-06-17) 110 | 111 | ### What's Changed 112 | 113 | * Documentation updates and test HomeKit server running by @dkerr64 in 114 | * Fix a bug in HomeKit library was not not always correctly updating characteristics by @jgstroud in Thanks @hjdhjd for pointing me in the right direction 115 | * Get status on boot by @jgstroud in 116 | * Make obstruction detection ignore spurrious detections by @jgstroud in 117 | 118 | ## v1.5.0 (2024-06-03) 119 | 120 | ### What's Changed 121 | 122 | * Firmware verification working branch by @dkerr64 in 123 | 124 | Fixes several critical bugs. Recommended for all users to upgrade. @dak64 identified some storage issues in the homekit library and corrected them. This should address #194 #189 #184 125 | 126 | **NOTE: You will need to re-pair with HomeKit after installing this update.** 127 | The most reliable way to do this is 1) erase from Apple Home, 2) reset/re-pair button on ratgdo, 3) kill Apple Home and restart it and 4) scan the QR code. 128 | 129 | ## v1.4.0 (2024-05-30) 130 | 131 | ### What's Changed 132 | 133 | * Fixes / features for next release. by @dkerr64 and @jgstroud in 134 | * Remove old redundant code 135 | * Save logs on clean shutdown 136 | * Check flash CRC on upload complete 137 | * Add a full flash verify on upload to make sure contents are written properly to flash 138 | * Change wifi persist to false. was writing wifi settings to flash multiple times on each boot. known to cause issues. #192 139 | 140 | Lots of changes mainly focuses on trying to prevent flash corruptions. 141 | 142 | ## v1.4.0 (2024-05-21) 143 | 144 | ### What's Changed 145 | 146 | * Fixes and features for next release by @dkerr64 in 147 | * Back out usage of secondary IRAM heap. This was causing some users to not be able to access the webUI. Fixes #173 148 | * Change webUI structure for memory optimization 149 | * Add script to remotely monitor logs 150 | * Document CLI control in the README 151 | * New prerelease check by @jgstroud in 152 | * v1.3.5 in changeling by @donavanbecker in 153 | * Wait before publishing release to discord by @donavanbecker in 154 | 155 | ## v1.3.5 (2024-05-01) 156 | 157 | ### What's Changed 158 | 159 | * Create CHANGELOG.md by @donavanbecker in #165 160 | * Add check for pre-releases in firmware update dialog by @dkerr64 in #166 161 | * Remove the visibility check as it was causing issues. by @jgstroud in #167 162 | 163 | ## Hotfix 164 | 165 | * New flash wear protection was causing problems with rolling code getting out of sync on some GDOs. Fix implemented 166 | * OTA Flash CRC check was causing false failures and blocking OTA upgrade. Removed this check. MD5 check still in place. 167 | 168 | Full Changelog: [v1.3.2...v1.3.5](https://github.com/ratgdo/homekit-ratgdo/compare/v1.3.2...v1.3.5) 169 | 170 | ## v1.3.2 (2024-04-30) 171 | 172 | ### What's Changed 173 | 174 | * Suspend certain activity when update underway, including comms and ho… by [@dkerr64](https://github.com/dkerr64) in [#153](https://github.com/ratgdo/homekit-ratgdo/pull/153) 175 | * Time-to-close and further memory improvements by [@dkerr64](https://github.com/dkerr64) in [#145](https://github.com/ratgdo/homekit-ratgdo/pull/145) 176 | * Include md5 in upload by [@donavanbecker](https://github.com/donavanbecker) in [#163](https://github.com/ratgdo/homekit-ratgdo/pull/163) 177 | Fixes #60 Warning is now configurable in the setting page. User can set the duration of the delay before close. Lights will flash, but no beep. Controlling the beep is not yet possible. 178 | 179 | Fixes [#150](https://github.com/ratgdo/homekit-ratgdo/issues/150) Allow user to set the WiFi transmit power. This combined with the ability to force 802.11g should fix [#77](https://github.com/ratgdo/homekit-ratgdo/issues/177) 180 | 181 | Added lots of additional error checking and safeguards to address [#151](https://github.com/ratgdo/homekit-ratgdo/issues/151) Note, you won't see the benefits of these changes until the next update. 182 | 183 | * More stability enhancements. Memory improvements to free up additional heap. 184 | * Store serial log to flash on crash 185 | * Allow logging over the network to the javascript console. No longer need a USB cable to capture the logs 186 | Many thanks to [@dkerr64](https://github.com/dkerr64) for all the work on this release. 187 | 188 | Full Changelog: [v1.2.1...v1.3.2](https://github.com/ratgdo/homekit-ratgdo/compare/v1.2.1...v1.3.2) 189 | 190 | ## v1.2.1 (2024-04-04) 191 | 192 | ### What's Changed 193 | 194 | Hk update and ota fix by [@jgstroud](https://github.com/jgstroud) in #152 195 | 196 | ### Hotfix release 197 | 198 | * Fixed incorrect reporting of garage door state 199 | * A number of users have reported failed OTA updates requiring a USB reflash in [#151](https://github.com/ratgdo/homekit-ratgdo/pull/151) 200 | I believe this was introduced by: [ee38a90](https://github.com/ratgdo/homekit-ratgdo/commit/ee38a9048e339e88cef1df21138e34102e10bdae) 201 | 202 | Reverted those changes 203 | 204 | NOTE: since the OTA update failure is a result of the running code and not the incoming code, you may have to flash this release with a USB cable as well, but hopefully this will fix any future OTAs. 205 | 206 | Full Changelog: [v1.2.0...v1.2.1](https://github.com/ratgdo/homekit-ratgdo/compare/v1.2.0...v1.2.1) 207 | 208 | ## v1.2.0 (2024-04-03) 209 | 210 | ### What's Changed 211 | 212 | * Fix Github typo by @SShah7433 in [#137](https://github.com/ratgdo/homekit-ratgdo/pull/137) 213 | * Add wifi RSSI to web page by [@jgstroud](https://github.com/jgstroud) in [#136](https://github.com/ratgdo/homekit-ratgdo/pull/136) 214 | * Include Elf file by [@donavanbecker](https://github.com/donavanbecker) in [#142](https://github.com/ratgdo/homekit-ratgdo/pull/142) 215 | * Improve web page stability by [@dkerr64](https://github.com/dkerr64) in [#139](https://github.com/ratgdo/homekit-ratgdo/pull/139) 216 | * Store crash dumps to flash by [@dkerr64](https://github.com/dkerr64) and [@jgstroud](https://github.com/jgstroud) 217 | * Changes to LwIP configuration to help prevent running out of memory when TCP connections die and when there are corrupt mDNS packets on the network by [@jgstroud](https://github.com/jgstroud) [#147](https://github.com/ratgdo/homekit-ratgdo/pull/147) 218 | * Update to HomeKit library to reduce memory footprint by [@jgstroud](https://github.com/jgstroud) [#148](https://github.com/ratgdo/homekit-ratgdo/pull/148) 219 | 220 | ### New Contributors 221 | 222 | * [@SShah7433](https://github.com/SShah7433) made their first contribution in [#137](https://github.com/ratgdo/homekit-ratgdo/pull/137) 223 | 224 | Full Changelog: [v1.1.0...v1.2.0](https://github.com/ratgdo/homekit-ratgdo/compare/v1.1.0...v1.2.0) 225 | 226 | ## v1.1.0 (2024-03-25) 227 | 228 | ### What's Changed 229 | 230 | * Improved pairing reliability by [@jgstroud](https://github.com/jgstroud) in [#135](https://github.com/ratgdo/homekit-ratgdo/pull/135) 231 | * Allow setting WiFi phy mode to 802.11B/G/N or auto by [@dkerr64](https://github.com/dkerr64) in [#133](https://github.com/ratgdo/homekit-ratgdo/pull/133) 232 | 233 | Some users with eero networks having connectivity issues have reported improved reliability by setting PHY mode to 802.11G 234 | 235 | Full Changelog: [v1.0.0...v1.1.0](https://github.com/ratgdo/homekit-ratgdo/compare/v1.0.0...v1.1.0) 236 | 237 | ## v1.1.0 (2024-03-19) 238 | 239 | ### Release 1.0 240 | 241 | We've focuses this release on stability and believe we are good to finally make an official 1.0 release. 242 | 243 | Many thanks to @thenewwazoo for starting this project and to [@dkerr64](https://github.com/dkerr64) for his work on helping get this release out. 244 | 245 | ### What's Changed 246 | 247 | Loads of stability improvements. 248 | 249 | * Use our own HomeKit server. by [@dkerr64](https://github.com/dkerr64) in [#127](https://github.com/ratgdo/homekit-ratgdo/pull/127) 250 | * Stability Improvements by [@jgstroud](https://github.com/jgstroud) in [#129](https://github.com/ratgdo/homekit-ratgdo/pull/129) 251 | 252 | Fixes [#15](https://github.com/ratgdo/homekit-ratgdo/issues/15), Fixes [#36](https://github.com/ratgdo/homekit-ratgdo/issues/36), Fixes [#94](https://github.com/ratgdo/homekit-ratgdo/issues/94), Fixes [#103](https://github.com/ratgdo/homekit-ratgdo/issues/103), Fixes [#126](https://github.com/ratgdo/homekit-ratgdo/issues/126), Fixes [#130](https://github.com/ratgdo/homekit-ratgdo/issues/130) 253 | 254 | Full Changelog: [v0.12.0...v1.0.0](https://github.com/ratgdo/homekit-ratgdo/compare/v0.12.0...v1.0.0) 255 | 256 | ## v0.12.0 (2024-03-08) 257 | 258 | ### What's Changed 259 | 260 | * Update Readme by [@donavanbecker](https://github.com/donavanbecker) in [#115](https://github.com/ratgdo/homekit-ratgdo/pull/115) 261 | * Update README by @thenewwazoo in [#123](https://github.com/ratgdo/homekit-ratgdo/pull/123) 262 | * Security 1.0 support, and web page updates by [@dkerr64](https://github.com/dkerr64) by @mitchjs in [#117](https://github.com/ratgdo/homekit-ratgdo/pull/117) 263 | * Add an option to auto reboot every X number of hours 264 | 265 | ### New Contributors 266 | 267 | * [@mitchjs](https://github.com/mitchjs) made their first contribution in [#117](https://github.com/ratgdo/homekit-ratgdo/pull/117) 268 | Full Changelog: [v0.11.0...v0.12.0](https://github.com/ratgdo/homekit-ratgdo/compare/v0.11.0...v0.12.0) 269 | 270 | ## v0.11.0 (2024-02-06) 271 | 272 | ### What's Changed 273 | 274 | * Add web authentication 275 | * Automatically detect new releases on github 276 | * Add door controls 277 | 278 | Default login admin/password 279 | 280 | * Discord Webhook after Release by [@donavanbecker](https://github.com/donavanbecker) in [#101](https://github.com/ratgdo/homekit-ratgdo/pull/101) 281 | * Web page updates by [@dkerr64](https://github.com/dkerr64) in [#107](https://github.com/ratgdo/homekit-ratgdo/pull/107) 282 | Full Changelog: [v0.10.0...v0.11.0](https://github.com/ratgdo/homekit-ratgdo/compare/v0.10.0...v0.11.0) 283 | 284 | ## v0.10.0 (2024-01-27) 285 | 286 | ### What's Changed 287 | 288 | The main reason for this release is to make the motion sensor service visible to only those that have one. After installing this release, you may have to re-pair your device to HK. If you have a motion detector, it won't show up initially, but should show up shortly after triggering it for the first time. From this point on, it will always be visible even after upgrades. 289 | 290 | * Update Release Process by [@donavanbecker](https://github.com/donavanbecker) in [#86](https://github.com/ratgdo/homekit-ratgdo/pull/86) 291 | * make the motion sensor dynamic. by [@jgstroud](https://github.com/jgstroud) in [#85](https://github.com/ratgdo/homekit-ratgdo/pull/85) 292 | * remove tag pattern check by [@donavanbecker](https://github.com/donavanbecker) in [396](https://github.com/ratgdo/homekit-ratgdo/pull/96) 293 | Full Changelog: [v0.9.0...v0.10.0](https://github.com/ratgdo/homekit-ratgdo/compare/v0.9.0...v0.10.0) 294 | 295 | ## v0.9.0 (2024-01-07) 296 | 297 | ### What's Changed 298 | 299 | * Add discord release notification by [@donavanbecker](https://github.com/donavanbecker) in [#83](https://github.com/ratgdo/homekit-ratgdo/pull/83) 300 | * Add OTA to Readme by [@donavanbecker](https://github.com/donavanbecker) in [#84](https://github.com/ratgdo/homekit-ratgdo/pull/84) 301 | * New webpage by [@dkerr64](https://github.com/dkerr64) in [#63](https://github.com/ratgdo/homekit-ratgdo/pull/63) 302 | Full Changelog: [v0.8.0...v0.9.0](https://github.com/ratgdo/homekit-ratgdo/compare/v0.8.0...v0.9.0) 303 | 304 | ## v0.8.0 (2024-01-04) 305 | 306 | ### What's Changed 307 | 308 | * Fix the millis timer veriable data types by [@jgstroud](https://github.com/jgstroud) in [#75](https://github.com/ratgdo/homekit-ratgdo/pull/75) 309 | * Fix a few duplicate log messages by [@tabacco](https://github.com/tabacco) in [#73](https://github.com/ratgdo/homekit-ratgdo/pull/73) 310 | * Pull in main branch of mrthiti's Homekit library to get mdns fix by [@jgstroud](https://github.com/jgstroud) in [#76](https://github.com/ratgdo/homekit-ratgdo/pull/76) 311 | * Add HTTPUpdateServer OTA support by [@sstoiana](https://github.com/sstoiana) and [@donavanbecker](https://github.com/donavanbecker) in [#72](https://github.com/ratgdo/homekit-ratgdo/pull/72) 312 | * Update Releasing Notes, & Enforce Version Pattern by [@donavanbecker](https://github.com/donavanbecker) in [#82](https://github.com/ratgdo/homekit-ratgdo/pull/82) 313 | Full Changelog: [v0.7.0...v0.8.0](https://github.com/ratgdo/homekit-ratgdo/compare/v0.7.0...v0.8.0) 314 | 315 | ## v0.7.0 (2024-01-01) 316 | 317 | ### What's Changed 318 | 319 | * Removes duplicate SSIDs from the wifi networks list, and shows only the one with the highest signal strength 320 | * Slightly improves responsiveness at first startup and when controlling lights 321 | * other minor improvements 322 | Thank you to all the contributors and testers! 323 | 324 | (no, you're not feeling deja-vu, this is a re-release in order to fix a new release process) 325 | Full Changelog: [v0.6.0...v0.7.0](https://github.com/ratgdo/homekit-ratgdo/compare/v0.6.0...v0.7.0) 326 | 327 | ## v0.6.0 (2023-12-14) 328 | 329 | ### What's Changed 330 | 331 | This release fixes garage door lock behavior and a crash on early setup, as well as adds support for directly sensing obstructions using the wired connection (versus relying on the garage door to report). 332 | 333 | Once again, many thanks to @jgstroud for doing all the work in this release, and to the many users testing and reporting issues. 334 | Full Changelog: [v0.5.0...v0.6.0](https://github.com/ratgdo/homekit-ratgdo/compare/v0.5.0...v0.6.0) 335 | 336 | ## v0.5.0 (2023-12-13) 337 | 338 | ### What's Changed 339 | 340 | Thanks to @jgstroud for adding locks and light support to this release! 341 | Full Changelog: [v0.4.0...v0.5.0](https://github.com/ratgdo/homekit-ratgdo/compare/v0.4.0...v0.5.0) 342 | 343 | ## v0.4.0 (2023-12-13) 344 | 345 | ### What's Changed 346 | 347 | This release enables multiple HomeKit-native RATGDOs to co-exist. 348 | 349 | NOTE: THIS IS A BREAKING RELEASE. You will need to re-pair your device after flashing. 350 | Full Changelog: [v0.3.1...v0.4.0](https://github.com/ratgdo/homekit-ratgdo/compare/v0.3.1...v0.4.0) 351 | 352 | ## v0.3.1 (2023-12-12) 353 | 354 | ### What's Changed 355 | 356 | This tweaks the motion sensor service notifications to save some redundant updates 357 | Full Changelog: [v0.3.0...v0.3.1](https://github.com/ratgdo/homekit-ratgdo/compare/v0.3.0...v0.3.1) 358 | 359 | ## v0.3.0 (2023-12-12) 360 | 361 | ### What's Changed 362 | 363 | New in this release is support for motion sensors. 364 | 365 | Full Changelog: [v0.2.3...v0.3.0](https://github.com/ratgdo/homekit-ratgdo/compare/v0.2.3...v0.3.0) 366 | 367 | ## v0.2.3 (2023-12-8) 368 | 369 | ### What's Changed 370 | 371 | * browser-based reboot button for the ratgdo device 372 | * tweaks to wifi setup to improve reliability 373 | * fixes to logic to make the Home UI more consistent 374 | 375 | Full Changelog: [v0.2.0...v0.2.3](https://github.com/ratgdo/homekit-ratgdo/compare/v0.2.0...v0.2.3) 376 | 377 | ## v0.2.0 (2023-12-06) 378 | 379 | ### What's Changed 380 | 381 | * Changes the pairing code to 2510-2023 382 | * Adds a scannable QR code to ease setup 383 | * Adds a web server that permits un-pairing with HomeKit and shows the QR code for pairing when not paired 384 | 385 | Full Changelog: [v0.1.0...v0.2.0](https://github.com/ratgdo/homekit-ratgdo/compare/v0.1.0...v0.2.0) 386 | 387 | ## v0.1.0 (2023-12-03) 388 | 389 | ### What's Changed 390 | 391 | * Improv support is still dicey (it will save the credentials but the device reboots and the page doesn't re-connect), but it will open and close a garage door pretty okay. 392 | 393 | Full Changelog: [v0.1.0]([https://github.com/ratgdo/homekit-ratgdo/compare/v0.1.0...v0.2.0](https://github.com/ratgdo/homekit-ratgdo/commits/v0.1.0)) 394 | -------------------------------------------------------------------------------- /auto_firmware_version.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import json 3 | 4 | Import("env") 5 | 6 | def get_firmware_specifier_build_flag(): 7 | f = open('./docs/manifest.json') 8 | data = json.load(f) 9 | f.close() 10 | build_version = data['version'].replace('v', '', 1) #remove letter v from front of version string 11 | build_flag = "-D AUTO_VERSION=\\\"" + build_version + "\\\"" 12 | print ("Firmware Revision: " + build_version) 13 | return (build_flag) 14 | 15 | env.Append( 16 | BUILD_FLAGS=[get_firmware_specifier_build_flag()] 17 | ) 18 | -------------------------------------------------------------------------------- /build_flags.py: -------------------------------------------------------------------------------- 1 | Import("env") 2 | 3 | # General options that are passed to the C and C++ compilers 4 | #env.Append(CCFLAGS=["-Wno-unused-variable"]) 5 | #env.Append(CCFLAGS=["flag1", "flag2"]) 6 | 7 | # General options that are passed to the C compiler (C only; not C++). 8 | #env.Append(CFLAGS=["flag1", "flag2"]) 9 | 10 | # General options that are passed to the C++ compiler 11 | env.Append(CXXFLAGS=["-fconcepts-ts"]) -------------------------------------------------------------------------------- /build_web_content.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # This script converts standard web content files (html, css, etc) into a C++ language 4 | # header file that is included in the program body. The files are compressed and use 5 | # PROGMEM keyword to store in Flash to save RAM. 6 | # 7 | # With thanks to https://github.com/mitchjs for removal of dependencies on external gzip/sed/xxd 8 | # 9 | # Copyright (c) 2023 David Kerr, https://github.com/dkerr64 10 | # 11 | import os 12 | import shutil 13 | import base64 14 | import zlib 15 | import gzip 16 | 17 | sourcepath = "src/www" 18 | targetpath = sourcepath + "/build" 19 | 20 | filenames = next(os.walk(sourcepath), (None, None, []))[2] 21 | print("Compressing and converting files from " + sourcepath + " into " + targetpath) 22 | 23 | # Start by deleting the target directory, then creating empty one. 24 | try: 25 | shutil.rmtree(targetpath) 26 | except FileNotFoundError: 27 | pass 28 | os.mkdir(targetpath) 29 | 30 | # calculate a CRC32 for each file and base64 encode it, this will change if the 31 | # file contents are changed. We use this to control browser caching. 32 | file_crc = {} 33 | for file in filenames: 34 | # skip hidden files 35 | if file[0] == ".": 36 | continue 37 | 38 | # skip status.json 39 | if file == "status.json": 40 | continue 41 | 42 | with open(sourcepath + "/" + file, "rb") as f: 43 | # read contents of the file 44 | data = f.read() 45 | crc32 = ( 46 | base64.urlsafe_b64encode(zlib.crc32(data).to_bytes(4, byteorder="big")) 47 | .decode() 48 | .replace("=", "") 49 | ) 50 | f.close() 51 | file_crc[file] = crc32 52 | print("CRC: " + crc32 + " (" + file + ")") 53 | 54 | # Open webcontent file and write warning header... 55 | wf = open(targetpath + "/webcontent.h", "w") 56 | wf.write("/**************************************\n") 57 | wf.write(" * Autogenerated DO NOT EDIT\n") 58 | wf.write(" **************************************/\n") 59 | wf.write("#include \n") 60 | wf.write("#include \n") 61 | wf.flush() 62 | 63 | varnames = [] 64 | # now loop through each file... 65 | for file in filenames: 66 | # skip hidden files 67 | if file[0] == ".": 68 | continue 69 | 70 | # skip status.json 71 | if file == "status.json": 72 | continue 73 | 74 | # create gzip file name 75 | gzfile = targetpath + "/" + file + ".gz" 76 | # create variable names 77 | varnames.append(("/" + file, gzfile.replace(".", "_").replace("/", "_").replace("-", "_"), file_crc[file])) 78 | # get file type 79 | t = file.rpartition(".")[-1] 80 | # if file matches, add true crc to ?v=CRC-32 marker and create the gzip 81 | if (t == "html") or (t == "htm") or (t == "js"): 82 | with open(sourcepath + "/" + file, 'rb') as f_in, gzip.open(gzfile, 'wb') as f_out: 83 | # read contents of the file 84 | data = f_in.read() 85 | # loop through each file that could be referenced 86 | for f_name, crc32 in file_crc.items(): 87 | # Replace the target string with real crc 88 | data = data.replace(bytes(f_name + "?v=CRC-32", 'utf-8'), bytes(f_name + "?v=" + crc32, 'utf-8')) 89 | f_out.write(data) 90 | f_out.close() 91 | else : 92 | with open(sourcepath + "/" + file, 'rb') as f_in, gzip.open(gzfile, 'wb') as f_out: 93 | f_out.writelines(f_in) 94 | f_out.close() 95 | 96 | # create the 'c' code 97 | # const unsigned char src_www_build_apple_touch_icon_png_gz[] PROGMEM = { 98 | # const unsigned int src_www_build_apple_touch_icon_png_gz_len = 2721; 99 | wf.write("const unsigned char %s[] PROGMEM = {\n" % gzfile.replace(".", "_").replace("/", "_").replace("-", "_") ) 100 | count = 0 101 | with open(gzfile, 'rb') as f: 102 | bytes_read = f.read(12) 103 | while bytes_read: 104 | count = count + len(bytes_read) 105 | wf.write(' ') 106 | for b in bytes_read: 107 | wf.write('0x%02X,' % b) 108 | wf.write('\n') 109 | bytes_read = f.read(12) 110 | 111 | wf.write('};\n') 112 | wf.write("const unsigned int %s_len = %d;\n\n" % (gzfile.replace(".", "_").replace("/", "_").replace("-", "_"), count) ) 113 | 114 | wf.flush() 115 | 116 | # Add possible MIME types to the file... 117 | wf.write( 118 | """ 119 | const char type_svg[] PROGMEM = "image/svg+xml"; 120 | const char type_bmp[] PROGMEM = "image/bmp"; 121 | const char type_gif[] PROGMEM = "image/gif"; 122 | const char type_jpeg[] PROGMEM = "image/jpeg"; 123 | const char type_jpg[] PROGMEM = "image/jpeg"; 124 | const char type_png[] PROGMEM = "image/png"; 125 | const char type_tiff[] PROGMEM = "image/tiff"; 126 | const char type_tif[] PROGMEM = "image/tiff"; 127 | const char type_txt[] PROGMEM = "text/plain"; 128 | const char type_[] PROGMEM = "text/plain"; 129 | const char type_htm[] PROGMEM = "text/html"; 130 | const char type_html[] PROGMEM = "text/html"; 131 | const char type_css[] PROGMEM = "text/css"; 132 | const char type_js[] PROGMEM = "text/javascript"; 133 | const char type_mjs[] PROGMEM = "text/javascript"; 134 | const char type_json[] PROGMEM = "application/json"; 135 | // Must be at least one more than max string above... 136 | #define MAX_MIME_TYPE_LEN 20 137 | 138 | """ 139 | ) 140 | 141 | # Use an unordered_map so we can lookup the data, length and type based on filename... 142 | wf.write( 143 | "const std::unordered_map> webcontent = {" 144 | ) 145 | n = 0 146 | for file, var, crc32 in varnames: 147 | t = "" 148 | if file.find(".") > 0: 149 | t = file.rpartition(".")[-1] 150 | # Need comma at end of every line except last one... 151 | if n > 0: 152 | wf.write(",") 153 | wf.write('\n { "' + file + '", {' + var + ", " + var + "_len, type_" + t + ', "' + crc32 + '"' + "} }") 154 | n = n + 1 155 | 156 | # All done, close the file... 157 | wf.write("\n};\n") 158 | wf.close() 159 | 160 | print("processed " + str(len(varnames)) + " files") 161 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | A repository for documentation. 2 | -------------------------------------------------------------------------------- /docs/build_size.log: -------------------------------------------------------------------------------- 1 | git checkout v0.6.0 2 | RAM: [===== ] 45.2% (used 37052 bytes from 81920 bytes) 3 | Flash: [===== ] 51.9% (used 542372 bytes from 1044464 bytes) 4 | 5 | git checkout v0.7.0 6 | RAM: [===== ] 51.9% (used 42532 bytes from 81920 bytes) 7 | Flash: [===== ] 52.5% (used 548276 bytes from 1044464 bytes) 8 | 9 | git checkout v0.8.0 10 | RAM: [===== ] 52.1% (used 42704 bytes from 81920 bytes) 11 | Flash: [===== ] 53.6% (used 559356 bytes from 1044464 bytes) 12 | 13 | git checkout v0.9.0 14 | RAM: [======= ] 71.7% (used 58748 bytes from 81920 bytes) 15 | Flash: [====== ] 55.8% (used 583144 bytes from 1044464 bytes) 16 | 17 | git checkout v0.10.0 18 | RAM: [======= ] 71.7% (used 58748 bytes from 81920 bytes) 19 | Flash: [====== ] 55.9% (used 583448 bytes from 1044464 bytes) 20 | 21 | git checkout v0.11.0 22 | RAM: [===== ] 47.6% (used 39012 bytes from 81920 bytes) 23 | Flash: [====== ] 55.8% (used 582968 bytes from 1044464 bytes) 24 | 25 | git checkout v0.12.0 26 | RAM: [===== ] 50.7% (used 41540 bytes from 81920 bytes) 27 | Flash: [====== ] 56.9% (used 594204 bytes from 1044464 bytes) 28 | 29 | git checkout v1.0.0 30 | RAM: [====== ] 55.4% (used 45408 bytes from 81920 bytes) 31 | Flash: [====== ] 56.6% (used 591640 bytes from 1044464 bytes) 32 | 33 | git checkout v1.1.0 34 | RAM: [====== ] 56.5% (used 46304 bytes from 81920 bytes) 35 | Flash: [====== ] 57.0% (used 595612 bytes from 1044464 bytes) 36 | 37 | git checkout v1.2.0 38 | RAM: [====== ] 55.7% (used 45612 bytes from 81920 bytes) 39 | Flash: [====== ] 56.6% (used 591188 bytes from 1044464 bytes) 40 | 41 | git checkout v1.2.1 42 | RAM: [====== ] 55.7% (used 45612 bytes from 81920 bytes) 43 | Flash: [====== ] 56.6% (used 591204 bytes from 1044464 bytes) 44 | 45 | git checkout v1.3.0 46 | RAM: [====== ] 55.7% (used 45612 bytes from 81920 bytes) 47 | Flash: [====== ] 56.6% (used 591236 bytes from 1044464 bytes) 48 | 49 | git checkout v1.3.1 50 | RAM: [===== ] 54.2% (used 44400 bytes from 81920 bytes) 51 | Flash: [====== ] 56.9% (used 593884 bytes from 1044464 bytes) 52 | 53 | git checkout v1.3.2 54 | RAM: [===== ] 54.2% (used 44400 bytes from 81920 bytes) 55 | Flash: [====== ] 56.9% (used 593884 bytes from 1044464 bytes) 56 | 57 | git checkout v1.3.3 58 | RAM: [===== ] 54.2% (used 44368 bytes from 81920 bytes) 59 | Flash: [====== ] 56.8% (used 593752 bytes from 1044464 bytes) 60 | 61 | git checkout v1.3.4 62 | RAM: [===== ] 54.2% (used 44400 bytes from 81920 bytes) 63 | Flash: [====== ] 56.9% (used 593884 bytes from 1044464 bytes) 64 | 65 | git checkout v1.3.5 66 | RAM: [===== ] 54.2% (used 44368 bytes from 81920 bytes) 67 | Flash: [====== ] 56.8% (used 593736 bytes from 1044464 bytes) 68 | 69 | git checkout v1.4.0 70 | RAM: [===== ] 52.8% (used 43216 bytes from 81920 bytes) 71 | Flash: [====== ] 56.9% (used 594272 bytes from 1044464 bytes) 72 | 73 | git checkout v1.4.1 74 | RAM: [===== ] 52.8% (used 43216 bytes from 81920 bytes) 75 | Flash: [====== ] 56.9% (used 594072 bytes from 1044464 bytes) 76 | 77 | git checkout v1.4.2 78 | git checkout v1.4.3 79 | git checkout v1.4.4 80 | git checkout v1.5.0 81 | 82 | git checkout v1.6.0 83 | RAM: [===== ] 53.1% (used 43476 bytes from 81920 bytes) 84 | Flash: [====== ] 57.2% (used 596936 bytes from 1044464 bytes) 85 | 86 | git checkout v1.6.1 87 | RAM: [===== ] 53.1% (used 43520 bytes from 81920 bytes) 88 | Flash: [====== ] 57.6% (used 601284 bytes from 1044464 bytes) 89 | 90 | 91 | git checkout v1.7.0 92 | RAM: [===== ] 53.2% (used 43544 bytes from 81920 bytes) 93 | Flash: [====== ] 57.5% (used 600500 bytes from 1044464 bytes) 94 | 95 | git checkout v1.7.1 96 | RAM: [====== ] 55.5% (used 45476 bytes from 81920 bytes) 97 | Flash: [====== ] 59.4% (used 620928 bytes from 1044464 bytes) -------------------------------------------------------------------------------- /docs/discord-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/discord-logo.png -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.1.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.1.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.10.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.10.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.11.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.11.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.12.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.12.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.2.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.2.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.2.1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.2.1.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.2.2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.2.2.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.2.3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.2.3.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.3.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.3.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.3.1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.3.1.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.4.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.4.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.5.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.5.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.6.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.6.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.7.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.7.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.8.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.8.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v0.9.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v0.9.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.0.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.0.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.1.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.1.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.2.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.2.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.2.0.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.2.0.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.2.1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.2.1.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.2.1.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.2.1.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.3.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.0.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.3.0.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.0.md5: -------------------------------------------------------------------------------- 1 | 6cbc32d72036e2757c4279ecfa3311af 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.3.1.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.1.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.3.1.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.1.md5: -------------------------------------------------------------------------------- 1 | 94e04d9d0f462a6c21a0e5784d555a9a 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.3.2.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.2.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.3.2.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.2.md5: -------------------------------------------------------------------------------- 1 | 78da3ee92d078d75f5ebe1aeda4988bf 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.3.3.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.3.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.3.3.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.3.md5: -------------------------------------------------------------------------------- 1 | 0a73c881259b43f8317816c5ffd8f448 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.4.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.3.4.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.4.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.3.4.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.4.md5: -------------------------------------------------------------------------------- 1 | 34768c5841c862e8682334bb3205c325 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.5.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.3.5.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.5.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.3.5.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.3.5.md5: -------------------------------------------------------------------------------- 1 | 5fa502ce2f86dd6a89aee974fb44aa50 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.4.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.0.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.4.0.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.0.md5: -------------------------------------------------------------------------------- 1 | 7ff9cb7fa97efc4565a3a109a4016a29 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.4.1.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.1.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.4.1.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.1.md5: -------------------------------------------------------------------------------- 1 | 412a51505d59792f7375398adf9480c2 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.4.2.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.2.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.4.2.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.2.md5: -------------------------------------------------------------------------------- 1 | fa3ba9c3fc429e402b4775cd519b52cd 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.4.3.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.3.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.4.3.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.3.md5: -------------------------------------------------------------------------------- 1 | 0508d08546f80aec977cc537de592565 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.4.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.4.4.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.4.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.4.4.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.4.4.md5: -------------------------------------------------------------------------------- 1 | d13a93c948222794eabfbf95f6b80334 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.5.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.5.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.5.0.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.5.0.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.5.0.md5: -------------------------------------------------------------------------------- 1 | 8cd5188662a63993e2692de234a4d735 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.6.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.6.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.6.0.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.6.0.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.6.0.md5: -------------------------------------------------------------------------------- 1 | d9b1199eaeb16ed103204f39a70ce9bf 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.6.1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.6.1.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.6.1.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.6.1.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.6.1.md5: -------------------------------------------------------------------------------- 1 | ac25eb918be794e32905050c3cf78434 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.7.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.7.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.7.0.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.7.0.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.7.0.md5: -------------------------------------------------------------------------------- 1 | f7cb5aedb4a624b8f4601821ba2d7ccc 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.7.1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.7.1.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.7.1.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.7.1.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.7.1.md5: -------------------------------------------------------------------------------- 1 | ebab410a6e01adf6535ab667406872e1 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.8.0.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.0.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.8.0.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.0.md5: -------------------------------------------------------------------------------- 1 | 9bf69feb0621ef0645794ca3a91131b6 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.8.1.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.1.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.8.1.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.1.md5: -------------------------------------------------------------------------------- 1 | 15dd429c67cd5317775a0f39aadee1a9 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.8.2.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.2.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.8.2.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.2.md5: -------------------------------------------------------------------------------- 1 | 4e2070d97f639a07564218f701ca8460 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.8.3.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.3.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.8.3.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.3.md5: -------------------------------------------------------------------------------- 1 | 7eb075bc03f8a083e7679c6bca58b9d8 2 | -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.4.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.8.4.bin -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.4.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/firmware/homekit-ratgdo-v1.8.4.elf -------------------------------------------------------------------------------- /docs/firmware/homekit-ratgdo-v1.8.4.md5: -------------------------------------------------------------------------------- 1 | 00c1307aae14e8bea3295285e25d6fe2 2 | -------------------------------------------------------------------------------- /docs/flash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

ratgdo web installer for homekit integrations

5 | 9 | 14 | 15 |

Important

16 |

This firmware is for the original ratgdo v2.5x series boards only.

17 |

HomeKit firmware for the ratgdo32 and ratgdo32-disco series boards can be found here.

18 | 19 |

ESP Home:

20 |

If you want to use ESPHome with Home Assistant instead of HomeKit, check out the ESPHome port.

21 | 22 |

For a comparison of available features for the different firmware options, please refer to the feature comparison matrix.

23 | 24 |

Drivers

25 |

If you can't connect to your ratgdo board make sure you have the right driver installed for the type of board you have.

26 |
    27 |
  • ratgdo v2.5i uses a CH340 USB to Serial chipset. [driver download]
  • 28 |
  • Most D1 Minis use an FTDI USB to Serial chipset. [driver download]
  • 29 |
30 | 31 |

This may take a while!

32 |

The setup process may take as long as a full minute, so please be patient.

33 | 34 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | You probably want the flasher page 2 | -------------------------------------------------------------------------------- /docs/logs/README.md: -------------------------------------------------------------------------------- 1 | A place to store examples of logs for future reference. 2 | -------------------------------------------------------------------------------- /docs/logs/motion.log: -------------------------------------------------------------------------------- 1 | This log illustrates how the motion sensor will send a Motion packet every ~5s 2 | --- 3 | 4 | >>> [ 14704] HomeKit: [Client 1073687572] Verification successful, secure session established 5 | >>> [ 14713] HomeKit: Free heap: 34464 6 | >>> [ 14753] HomeKit: [Client 1073687572] Get Accessories 7 | >>> [ 14790] RATGDO: get active: 1 8 | >>> [ 14795] RATGDO: get current door state: 1 9 | >>> [ 14800] RATGDO: get target door state: 1 10 | >>> [ 14808] RATGDO: get obstruction: 0 11 | >>> [ 14864] HomeKit: [Client 1073687572] Get Characteristics 12 | >>> [ 14892] HomeKit: [Client 1073687572] Get Characteristics 13 | >>> [ 14913] HomeKit: [Client 1073687572] Update Characteristics 14 | >>> [ 14950] HomeKit: [Client 1073687572] Get Characteristics 15 | >>> [ 14969] HomeKit: [Client 1073687572] Get Characteristics 16 | >>> [ 26034] RATGDO: reader completed packet 17 | >>> [ 26038] RATGDO: DECODED 000022D7 000000C27594B914 0000F085 18 | >>> [ 26045] RATGDO: PACKET(0x94B914 @ 0x22D7) Motion - NoData: [Zero: 0x00000000, Parity: 0xF] 19 | >>> [ 26053] RATGDO: Motion Detected 20 | >>> [ 26057] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 21 | >>> [ 30945] RATGDO: reader completed packet 22 | >>> [ 30950] RATGDO: DECODED 000022D7 000000C27594B914 0000F085 23 | >>> [ 30956] RATGDO: PACKET(0x94B914 @ 0x22D7) Motion - NoData: [Zero: 0x00000000, Parity: 0xF] 24 | >>> [ 30964] RATGDO: Motion Detected 25 | >>> [ 30968] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 26 | >>> [ 35969] RATGDO: Motion Cleared 27 | >>> [ 35972] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 28 | >>> [ 40385] RATGDO: reader completed packet 29 | >>> [ 40389] RATGDO: DECODED 000022D7 000000C27594B914 0000F085 30 | >>> [ 40395] RATGDO: PACKET(0x94B914 @ 0x22D7) Motion - NoData: [Zero: 0x00000000, Parity: 0xF] 31 | >>> [ 40404] RATGDO: Motion Detected 32 | >>> [ 40408] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 33 | >>> [ 45295] RATGDO: reader completed packet 34 | >>> [ 45299] RATGDO: DECODED 000022D7 000000C27594B914 0000F085 35 | >>> [ 45305] RATGDO: PACKET(0x94B914 @ 0x22D7) Motion - NoData: [Zero: 0x00000000, Parity: 0xF] 36 | >>> [ 45314] RATGDO: Motion Detected 37 | >>> [ 45317] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 38 | >>> [ 50203] RATGDO: reader completed packet 39 | >>> [ 50207] RATGDO: DECODED 000022D7 000000C27594B914 0000F085 40 | >>> [ 50214] RATGDO: PACKET(0x94B914 @ 0x22D7) Motion - NoData: [Zero: 0x00000000, Parity: 0xF] 41 | >>> [ 50222] RATGDO: Motion Detected 42 | >>> [ 50226] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 43 | >>> [ 55114] RATGDO: reader completed packet 44 | >>> [ 55118] RATGDO: DECODED 000022D7 000000C27594B914 0000F085 45 | >>> [ 55125] RATGDO: PACKET(0x94B914 @ 0x22D7) Motion - NoData: [Zero: 0x00000000, Parity: 0xF] 46 | >>> [ 55133] RATGDO: Motion Detected 47 | >>> [ 55137] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 48 | >>> [ 60022] RATGDO: reader completed packet 49 | >>> [ 60026] RATGDO: DECODED 000022D7 000000C27594B914 0000F085 50 | >>> [ 60032] RATGDO: PACKET(0x94B914 @ 0x22D7) Motion - NoData: [Zero: 0x00000000, Parity: 0xF] 51 | >>> [ 60041] RATGDO: Motion Detected 52 | >>> [ 60045] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 53 | >>> [ 64972] RATGDO: reader completed packet 54 | >>> [ 64977] RATGDO: DECODED 000022D7 000000C27594B914 0000F085 55 | >>> [ 64983] RATGDO: PACKET(0x94B914 @ 0x22D7) Motion - NoData: [Zero: 0x00000000, Parity: 0xF] 56 | >>> [ 64992] RATGDO: Motion Detected 57 | >>> [ 64995] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 58 | >>> [ 69996] RATGDO: Motion Cleared 59 | >>> [ 69999] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 60 | >>> [ 72359] RATGDO: reader completed packet 61 | >>> [ 72364] RATGDO: DECODED 000022D7 000000C27594B914 0000F085 62 | >>> [ 72370] RATGDO: PACKET(0x94B914 @ 0x22D7) Motion - NoData: [Zero: 0x00000000, Parity: 0xF] 63 | >>> [ 72379] RATGDO: Motion Detected 64 | >>> [ 72382] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 65 | >>> [ 75367] HomeKit: [Client 1073687572] Get Characteristics 66 | >>> [ 77268] RATGDO: reader completed packet 67 | >>> [ 77273] RATGDO: DECODED 000022D7 000000C27594B914 0000F085 68 | >>> [ 77279] RATGDO: PACKET(0x94B914 @ 0x22D7) Motion - NoData: [Zero: 0x00000000, Parity: 0xF] 69 | >>> [ 77288] RATGDO: Motion Detected 70 | >>> [ 77291] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 71 | >>> [ 82176] RATGDO: reader completed packet 72 | >>> [ 82180] RATGDO: DECODED 000022D7 000000C27594B914 0000F085 73 | >>> [ 82187] RATGDO: PACKET(0x94B914 @ 0x22D7) Motion - NoData: [Zero: 0x00000000, Parity: 0xF] 74 | >>> [ 82195] RATGDO: Motion Detected 75 | >>> [ 82199] HomeKit: [Client 1073687572] Got characteristic 1.16 change event 76 | -------------------------------------------------------------------------------- /docs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "homekit-ratgdo", 3 | "version": "v1.8.4", 4 | "new_install_prompt_erase": true, 5 | "new_install_improv_wait_time": 60, 6 | "builds": [ 7 | { 8 | "chipFamily": "ESP8266", 9 | "parts": [ 10 | { 11 | "path": "firmware/homekit-ratgdo-v1.8.4.bin", 12 | "offset": 0 13 | } 14 | ] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /docs/ota/firmware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/ota/firmware.png -------------------------------------------------------------------------------- /docs/ota/ota.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/ota/ota.png -------------------------------------------------------------------------------- /docs/ota/updatecrc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/ota/updatecrc.png -------------------------------------------------------------------------------- /docs/ota/updatefail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/ota/updatefail.png -------------------------------------------------------------------------------- /docs/ota/uploaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/ota/uploaded.png -------------------------------------------------------------------------------- /docs/qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/qr.png -------------------------------------------------------------------------------- /docs/releasing.md: -------------------------------------------------------------------------------- 1 | The release process 2 | === 3 | 4 | Once a release is ready, all you have to do is create a GitHub Release and tag. Once that release is created it will kick off the `.github/workflows/manifest.yml` GitHub Action. This workflow will do all the work for you to get a new release out. It will create an updated manifest.json file with the new version from the tag that you ceated with the release. Once that workflow completes it then triggers another workflow, `.github/workflows/build.yml`, this workflow will create the firmware.bin file following the format of `homekit-ratgdo-${{ latest-tag }}.bin`. 5 | 6 | When the firmware and manifest.json is updated, this will automatically update the flasher page as the /docs folder is what the flasher page looks at so no need to update anything in this folder. 7 | 8 | Releases tag should be named v[0-9]+.[0-9]+.[0-9], example: `v0.7.0`. the workflows are designed to follows this pattern, if this pattern isn't followed then the workflow will stop. 9 | -------------------------------------------------------------------------------- /docs/syncing.md: -------------------------------------------------------------------------------- 1 | The sync process 2 | === 3 | 4 | Every packet sent to the garage door opener includes a client ID and a rolling code. The client ID 5 | does not (read: is not expected to) change over time and presumably uniquely identifies a device on 6 | the bus. Each subsequent packet's rolling code must be higher than the last one sent, for the given 7 | client ID. 8 | 9 | When a newly-installed device on the bus wishes to register itself with the garage door opener, it 10 | "handshakes" by sending two commands. The first packet is sent with its client ID and an arbitrary 11 | rolling code. The second is sent with its client ID and the arbitrary rolling code plus one (other 12 | values may also work, I have not tested it). The packets are not (afaik) important. The original 13 | MQTT firmware sent a bunch of packets of various types. The ESPHome firmware sends two: GetStatus 14 | and Openings, which the HomeKit firmware copies, mostly. 15 | 16 | The important thing to know is that the first packet is *ignored*. Future packets get a response. 17 | 18 | The "sync" process performed at startup is not actually required for an established client ID, but 19 | if the rolling code is lost or corrupted there is no way to "reset" it. Since the sync itself is 20 | simply "send some packets", we just do it at startup anyway. In this case (of an already-established 21 | client ID), all sync packets get a response. 22 | 23 | So why does this firmware send Openings and then GetStatus? 24 | 25 | At program start the GarageDoor struct is initialized with zeroes. The zero value, to HomeKit, means 26 | "garage door open", but the specific meaning isn't important, since there's a 50% chance it's wrong 27 | anyway. Note that HomeKit has no value to signify "unknown" or "not yet". This means we need to get 28 | a GetStatus response ASAP. 29 | 30 | In the case of introducing a new client ID, the first packet is ignored. So why not send two 31 | GetStatus commands? There is an untested-by-me claim that the garage door will ignore duplicate 32 | packets sent within some indeterminate short period of time. We avoid this problem by sending an 33 | Openings packet as the first. We need status information, so we send a GetStatus packet next, 34 | which garners a response. 35 | 36 | The delay between the packets was basically just pulled out of thin air. 37 | -------------------------------------------------------------------------------- /docs/webpage/password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/webpage/password.png -------------------------------------------------------------------------------- /docs/webpage/rebootcrc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/webpage/rebootcrc.png -------------------------------------------------------------------------------- /docs/webpage/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/webpage/settings.png -------------------------------------------------------------------------------- /docs/webpage/webpage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/docs/webpage/webpage.png -------------------------------------------------------------------------------- /improv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import serial 3 | import sys 4 | import argparse 5 | from enum import Enum 6 | 7 | improv_header="IMPROV" 8 | improv_version=1 9 | verbose=False 10 | 11 | class improv_type(Enum): 12 | curr_state=1 13 | error_state=2 14 | rpc_cmd=3 15 | rpc_result=4 16 | 17 | class rpc_cmd(Enum): 18 | wifi_cmd_id=1 19 | current_state_cmd_id=2 20 | device_info_cmd_id=3 21 | scan_cmd_id=4 22 | 23 | def send_improv_cmd(ser, cmd): 24 | improv_cmd=list(improv_header) 25 | improv_cmd.append(improv_version) 26 | improv_cmd.append(improv_type.rpc_cmd.value) 27 | improv_cmd.append(len(cmd)) 28 | improv_cmd += cmd 29 | improv_cmd.append(0) 30 | 31 | tx_cmd=[] 32 | 33 | cksum=0 34 | for i in improv_cmd: 35 | if isinstance(i, int): 36 | cksum += i 37 | tx_cmd.append(i) 38 | else: 39 | cksum += ord(i) 40 | tx_cmd.append(ord(i)) 41 | 42 | tx_cmd[-1] = cksum & 0xff 43 | if verbose: 44 | print(bytearray(tx_cmd)) 45 | ser.write(bytearray(tx_cmd)) 46 | ser.write(bytearray([ord('\n')])) 47 | ser.flush() 48 | 49 | def set_wifi(ser, ssid, passwd): 50 | wifi_cmd=[rpc_cmd.wifi_cmd_id.value] 51 | wifi_cmd.append(len(ssid)+len(passwd)+2) 52 | wifi_cmd.append(len(ssid)) 53 | wifi_cmd += list(ssid) 54 | wifi_cmd.append(len(passwd)) 55 | wifi_cmd += list(passwd) 56 | 57 | send_improv_cmd(ser, wifi_cmd) 58 | monitor(ser) 59 | 60 | def scan_wifi(ser): 61 | wifi_cmd=[rpc_cmd.scan_cmd_id.value, 0] 62 | 63 | send_improv_cmd(ser, wifi_cmd) 64 | monitor(ser) 65 | 66 | def dev_info(ser): 67 | wifi_cmd=[rpc_cmd.device_info_cmd_id.value, 0] 68 | 69 | send_improv_cmd(ser, wifi_cmd) 70 | monitor(ser) 71 | 72 | def dev_state(ser): 73 | wifi_cmd=[rpc_cmd.current_state_cmd_id.value, 0] 74 | 75 | send_improv_cmd(ser, wifi_cmd) 76 | monitor(ser) 77 | 78 | def monitor(ser): 79 | class states(Enum): 80 | gethdr=0 81 | getver=1 82 | gettype=2 83 | getlen=3 84 | getdata=4 85 | getrpcresp=5 86 | getdatlen=6 87 | getstrlen=7 88 | getrpcdat=8 89 | 90 | state=states.gethdr 91 | 92 | imp_type=0 93 | rx_header="" 94 | while True: 95 | bs = ser.read(1) 96 | #if verbose > 1: 97 | # print(bs, end="") 98 | ch=int.from_bytes(bs, "big") 99 | 100 | if state == states.gethdr: 101 | try: 102 | rx_header += chr(ch) 103 | if len(rx_header) > len(improv_header): 104 | rx_header = rx_header[1:] 105 | if rx_header == improv_header: 106 | state=states.getver 107 | rx_header="" 108 | except: 109 | rx_header = "" 110 | 111 | elif state == states.getver: 112 | if ch==improv_version: 113 | state=states.gettype 114 | else: 115 | state=states.gethdr 116 | 117 | elif state == states.gettype: 118 | if verbose: 119 | print("type", ch) 120 | imp_type=ch 121 | state=states.getlen 122 | 123 | elif state == states.getlen: 124 | if verbose: 125 | print("len", ch) 126 | cmdlen=ch 127 | if cmdlen > 0: 128 | if imp_type == improv_type.rpc_result.value: 129 | state=states.getrpcresp 130 | else: 131 | state=states.getdata 132 | else: 133 | state=states.gethdr 134 | 135 | elif state == states.getrpcresp: 136 | if verbose: 137 | print("response to command", ch) 138 | resp_cmd=ch 139 | state=state.getdatlen 140 | 141 | elif state == states.getdatlen: 142 | if verbose: 143 | print("datlen", ch) 144 | datlen=ch 145 | if datlen == 0: 146 | return 147 | state=state.getstrlen 148 | 149 | elif state == states.getstrlen: 150 | if verbose: 151 | print("strlen", ch) 152 | datlen -= 1 153 | strlen=ch 154 | state=state.getrpcdat 155 | 156 | elif state == states.getrpcdat: 157 | print(chr(ch), end="") 158 | strlen -= 1 159 | datlen -= 1 160 | if strlen == 0: 161 | if datlen <= 0: 162 | print() 163 | if resp_cmd == rpc_cmd.scan_cmd_id.value: 164 | state=states.gethdr 165 | else: 166 | return 167 | else: 168 | print(" ", end="") 169 | state=states.getstrlen 170 | sys.stdout.flush() 171 | 172 | elif state == states.getdata: 173 | if imp_type == improv_type.curr_state.value: 174 | if (ch == 0): 175 | print("WiFi stopped") 176 | return 177 | elif (ch == 1): 178 | print("Wifi awaiting authorization") 179 | elif (ch == 2): 180 | print("Wifi authorized") 181 | elif (ch == 3): 182 | print("Wifi provisioning") 183 | elif (ch == 4): 184 | print("Wifi provisioned") 185 | state=states.gethdr 186 | elif imp_type == improv_type.error_state.value: 187 | if (ch == 0): 188 | print("No Error") 189 | elif (ch == 1): 190 | print("Invalid RPC packet") 191 | elif (ch == 2): 192 | print("Unknown RPC command") 193 | elif (ch == 3): 194 | print("Unable to connect") 195 | elif (ch == 4): 196 | print("Unknown Error") 197 | return 198 | 199 | else: 200 | state=states.gethdr 201 | 202 | else: 203 | if verbose: 204 | try: 205 | print(bs.decode(), end="") 206 | except: 207 | print(bs, end="") 208 | sys.stdout.flush() 209 | 210 | 211 | if __name__ == '__main__': 212 | parser = argparse.ArgumentParser() 213 | parser.add_argument('-d', dest='dev', help="USB Device") 214 | parser.add_argument('-s', dest='ssid', help="Wifi SSID") 215 | parser.add_argument('-p', dest='passwd', help="Wifi Password") 216 | parser.add_argument('-S', dest='scan', default=False, action=argparse.BooleanOptionalAction, help="Scan Wifi Networks") 217 | parser.add_argument('-i', dest='info', default=False, action=argparse.BooleanOptionalAction, help="Get device information") 218 | parser.add_argument('-g', dest='getstate', default=False, action=argparse.BooleanOptionalAction, help="Get current state") 219 | parser.add_argument('-v', dest='verbose', default=False, action=argparse.BooleanOptionalAction, help="Verbose") 220 | args = parser.parse_args() 221 | 222 | verbose = args.verbose 223 | 224 | if (not args.dev): 225 | print("Must select serial device") 226 | exit() 227 | 228 | ser = serial.Serial(args.dev, 115200) 229 | if (not ser): 230 | print("Invalid serial device") 231 | exit() 232 | 233 | if (args.scan): 234 | scan_wifi(ser) 235 | 236 | if (args.info): 237 | dev_info(ser) 238 | 239 | if (args.getstate): 240 | dev_state(ser) 241 | 242 | if (args.ssid and args.passwd): 243 | set_wifi(ser, args.ssid, args.passwd) 244 | 245 | -------------------------------------------------------------------------------- /lib/lwip2/README.md: -------------------------------------------------------------------------------- 1 | # Custom LWIP build 2 | This library is an exact copy of the one used in the PlatformIO Arduino framework but sets the maximum number of TCP retries to 2. 3 | 4 | This library was built from: 5 | https://github.com/jgstroud/Arduino/tree/maxrtx 6 | 7 | -------------------------------------------------------------------------------- /lib/lwip2/liblwip2-536.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/lib/lwip2/liblwip2-536.a -------------------------------------------------------------------------------- /lib/ratgdo/Reader.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Brandon Matthews 2 | // All rights reserved. GPLv3 License 3 | 4 | #ifndef _READER_H 5 | #define _READER_H 6 | 7 | #include 8 | #include "log.h" 9 | 10 | enum SecPlus2ReaderMode : uint8_t { 11 | SCANNING, 12 | RECEIVING, 13 | }; 14 | 15 | class SecPlus2Reader { 16 | private: 17 | bool m_is_reading = false; 18 | uint32_t m_msg_start = 0; 19 | size_t m_byte_count = 0; 20 | uint8_t m_rx_buf[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00}; 21 | SecPlus2ReaderMode m_mode = SCANNING; 22 | 23 | public: 24 | SecPlus2Reader() = default; 25 | 26 | bool push_byte(uint8_t inp) { 27 | bool msg_ready = false; 28 | 29 | switch (m_mode) { 30 | case SCANNING: 31 | m_msg_start <<= 8; 32 | m_msg_start |= inp; 33 | m_msg_start &= 0x00FFFFFF; 34 | 35 | if (m_msg_start == SECPLUS2_PREAMBLE) { 36 | m_byte_count = 3; 37 | m_mode = RECEIVING; 38 | } 39 | break; 40 | 41 | case RECEIVING: 42 | m_rx_buf[m_byte_count] = inp; 43 | m_byte_count += 1; 44 | 45 | if (m_byte_count == SECPLUS2_CODE_LEN) { 46 | m_mode = SCANNING; 47 | m_msg_start = 0; 48 | msg_ready = true; 49 | } 50 | break; 51 | } 52 | 53 | if (msg_ready) { 54 | RINFO("reader completed packet"); 55 | } 56 | return msg_ready; 57 | }; 58 | 59 | uint8_t const * const fetch_buf(void) { 60 | return m_rx_buf; 61 | } 62 | }; 63 | 64 | #endif // _READER_H 65 | -------------------------------------------------------------------------------- /lib/ratgdo/log.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Brandon Matthews 2 | // All rights reserved. GPLv3 License 3 | 4 | #ifndef _LOG_H 5 | #define _LOG_H 6 | 7 | #include 8 | #include 9 | #include "secplus2.h" 10 | #include 11 | 12 | void print_packet(uint8_t pkt[SECPLUS2_CODE_LEN]); 13 | 14 | // #define LOG_MSG_BUFFER 15 | 16 | #ifdef LOG_MSG_BUFFER 17 | 18 | #define CRASH_LOG_MSG_FILE "crash_log" 19 | #define REBOOT_LOG_MSG_FILE "reboot_log" 20 | #if defined(MMU_IRAM_HEAP) 21 | // This can be large, but not too large. IRAM heap is approx 18KB, we also need 22 | // space for other data in here, so during development monitor logs and adjust 23 | // this smaller if necessary. IRAM malloc's are all done during startup. 24 | #define LOG_BUFFER_SIZE 8192 25 | #else 26 | #define LOG_BUFFER_SIZE 1024 27 | #endif 28 | 29 | typedef struct logBuffer 30 | { 31 | uint16_t wrapped; // two bytes 32 | uint16_t head; // two bytes 33 | char buffer[LOG_BUFFER_SIZE - 4]; // sized so whole struct is LOG_BUFFER_SIZE bytes 34 | } logBuffer; 35 | 36 | extern "C" void logToBuffer_P(const char *fmt, ...); 37 | void printSavedLog(File file, Print &outDevice = Serial); 38 | void printSavedLog(Print &outDevice = Serial); 39 | void printMessageLog(Print &outDevice = Serial); 40 | void crashCallback(); 41 | 42 | #define RATGDO_PRINTF(message, ...) logToBuffer_P(PSTR(message), ##__VA_ARGS__) 43 | 44 | #define RINFO(message, ...) RATGDO_PRINTF(">>> [%7lu] RATGDO: " message "\r\n", millis(), ##__VA_ARGS__) 45 | #define RERROR(message, ...) RATGDO_PRINTF("!!! [%7lu] RATGDO: " message "\r\n", millis(), ##__VA_ARGS__) 46 | #else // LOG_MSG_BUFFER 47 | 48 | #ifndef UNIT_TEST 49 | 50 | #define RINFO(message, ...) XPGM_PRINTF(">>> [%7lu] RATGDO: " message "\r\n", millis(), ##__VA_ARGS__) 51 | #define RERROR(message, ...) XPGM_PRINTF("!!! [%7lu] RATGDO: " message "\r\n", millis(), ##__VA_ARGS__) 52 | 53 | #else // UNIT_TEST 54 | 55 | #include 56 | #define RINFO(message, ...) printf(">>> RATGDO: " message "\n", ##__VA_ARGS__) 57 | #define RERROR(message, ...) printf("!!! RATGDO: " message "\n", ##__VA_ARGS__) 58 | 59 | #endif // UNIT_TEST 60 | 61 | #endif // LOG_MSG_BUFFER 62 | 63 | #endif // _LOG_H 64 | -------------------------------------------------------------------------------- /lib/ratgdo/secplus2.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Brandon Matthews 2 | // All rights reserved. GPLv3 License 3 | 4 | #ifndef _SECPLUS2_H 5 | #define _SECPLUS2_H 6 | 7 | const uint8_t SECPLUS2_CODE_LEN = 19; 8 | const uint32_t SECPLUS2_PREAMBLE = 0x00550100; 9 | 10 | #endif // _SECPLUS2_H 11 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env] 12 | upload_speed = 921600 13 | monitor_speed = 115200 ; must remain at 115200 for improv 14 | ;monitor_speed = 74880 ; hardware default 15 | board_build.filesystem = littlefs 16 | 17 | [env:ratgdo_esp8266_hV25] 18 | framework = arduino 19 | platform = espressif8266 20 | board = d1_mini 21 | board_build.ldscript = eagle.flash.4m2m.ld 22 | build_flags = 23 | -Llib/lwip2 24 | -llwip2-536 25 | ${env.build_flags} 26 | '-Wno-unused-variable' 27 | -D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY_LOW_FLASH 28 | -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED 29 | -D LOG_MSG_BUFFER 30 | -D ENABLE_CRASH_LOG 31 | -D NTP_CLIENT 32 | -D USE_NTP_TIMESTAMP 33 | -D LEGACY_SETTINGS_MIGRATION 34 | ; -D HOMEKIT_USE_IRAM 35 | -D GW_PING_CHECK 36 | ; -D CRASH_DEBUG 37 | ; -D CHUNK_WEB_PAGES 38 | ; -D DEBUG_UPDATER=Serial 39 | monitor_filters = esp8266_exception_decoder 40 | lib_deps = 41 | https://github.com/dkerr64/Arduino-HomeKit-ESP8266.git#de4f989f5329de49c16bd40e7660e129fee976f7 42 | https://github.com/jgstroud/EspSaveCrash.git#cf2803abfa51a83c93548f2591d4564a47845a72 43 | esphome/Improv@^1.2.3 44 | https://github.com/ratgdo/espsoftwareserial.git#autobaud 45 | dancol90/ESP8266Ping@^1.1.0 46 | mathertel/OneButton@^2.6.1 47 | lib_ldf_mode = deep+ 48 | extra_scripts = 49 | build_flags.py 50 | pre:build_web_content.py 51 | pre:auto_firmware_version.py 52 | -------------------------------------------------------------------------------- /reboot.sh: -------------------------------------------------------------------------------- 1 | curl -s -X POST http://$1/reboot 2 | -------------------------------------------------------------------------------- /src/comms.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Brandon Matthews 2 | // All rights reserved. GPLv3 License 3 | 4 | #ifndef _COMMS_H 5 | #define _COMMS_H 6 | //Needed to define doorState 7 | #include "Packet.h" 8 | 9 | void setup_comms(); 10 | void comms_loop(); 11 | 12 | void open_door(); 13 | void close_door(); 14 | 15 | void set_lock(uint8_t value); 16 | void set_light(bool value); 17 | 18 | void save_rolling_code(); 19 | void reset_door(); 20 | 21 | //Adding external declaration so doorState can be used in dryContactLoop() ratgdo.cpp 22 | //Remove if dryContactLoop() is moved 23 | extern DoorState doorState; 24 | 25 | #endif // _COMMS_H 26 | -------------------------------------------------------------------------------- /src/homekit.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Brandon Matthews 2 | // All rights reserved. GPLv3 License 3 | 4 | #include 5 | #include "ratgdo.h" 6 | #include "comms.h" 7 | #include "log.h" 8 | #include 9 | #include "utilities.h" 10 | #include "homekit_decl.h" 11 | #include "web.h" 12 | 13 | // Bring in config and characteristics defined in homekit_decl.c 14 | extern "C" homekit_server_config_t config; 15 | extern "C" homekit_characteristic_t current_door_state; 16 | extern "C" homekit_characteristic_t target_door_state; 17 | extern "C" homekit_characteristic_t obstruction_detected; 18 | extern "C" homekit_characteristic_t active_state; 19 | extern "C" homekit_characteristic_t current_lock_state; 20 | extern "C" homekit_characteristic_t target_lock_state; 21 | extern "C" homekit_characteristic_t light_state; 22 | extern "C" homekit_characteristic_t motion_detected; 23 | 24 | // Bring in the garage door state storage in ratgdo.c 25 | extern GarageDoor garage_door; 26 | 27 | // Forward-declare setters used by characteristics 28 | homekit_value_t current_door_state_get(); 29 | homekit_value_t target_door_state_get(); 30 | void target_door_state_set(const homekit_value_t new_value); 31 | homekit_value_t obstruction_detected_get(); 32 | homekit_value_t active_state_get(); 33 | homekit_value_t current_lock_state_get(); 34 | homekit_value_t target_lock_state_get(); 35 | void target_lock_state_set(const homekit_value_t new_value); 36 | homekit_value_t light_state_get(); 37 | void light_state_set(const homekit_value_t value); 38 | 39 | // Make serial_number available 40 | extern "C" char serial_number[SERIAL_NAME_SIZE]; 41 | 42 | /********************************** MAIN LOOP CODE *****************************************/ 43 | 44 | void homekit_loop() 45 | { 46 | loop_id = LOOP_HK; 47 | arduino_homekit_loop(); 48 | } 49 | 50 | void setup_homekit() 51 | { 52 | RINFO("=== Starting HomeKit Server"); 53 | String macAddress = WiFi.macAddress(); 54 | snprintf(serial_number, SERIAL_NAME_SIZE, "%s", macAddress.c_str()); 55 | 56 | current_door_state.getter = current_door_state_get; 57 | target_door_state.getter = target_door_state_get; 58 | target_door_state.setter = target_door_state_set; 59 | obstruction_detected.getter = obstruction_detected_get; 60 | active_state.getter = active_state_get; 61 | current_lock_state.getter = current_lock_state_get; 62 | target_lock_state.getter = target_lock_state_get; 63 | target_lock_state.setter = target_lock_state_set; 64 | light_state.getter = light_state_get; 65 | light_state.setter = light_state_set; 66 | 67 | garage_door.has_motion_sensor = (bool)read_int_from_file("has_motion"); 68 | if (!garage_door.has_motion_sensor && (userConfig->motionTriggers == 0)) 69 | { 70 | RINFO("Motion Sensor not detected. Disabling Service"); 71 | config.accessories[0]->services[3] = NULL; 72 | } 73 | if (userConfig->gdoSecurityType == 3) 74 | { 75 | RINFO("Dry contact does not support light control. Disabling Service"); 76 | config.accessories[0]->services[2] = NULL; 77 | } 78 | 79 | // We can set current lock state to unknown as HomeKit has value for that. 80 | // But we can't do the same for door state as HomeKit has no value for that. 81 | garage_door.current_lock = CURR_UNKNOWN; 82 | arduino_homekit_setup(&config); 83 | IRAM_START 84 | // Doing a IRAM start/end just to log free memory 85 | IRAM_END("HomeKit server started"); 86 | } 87 | 88 | /******************************** GETTERS AND SETTERS ***************************************/ 89 | 90 | homekit_value_t current_door_state_get() 91 | { 92 | RINFO("get current door state: %d", garage_door.current_state); 93 | 94 | return HOMEKIT_UINT8_CPP(garage_door.current_state); 95 | } 96 | 97 | homekit_value_t target_door_state_get() 98 | { 99 | RINFO("get target door state: %d", garage_door.target_state); 100 | 101 | return HOMEKIT_UINT8_CPP(garage_door.target_state); 102 | } 103 | 104 | void target_door_state_set(const homekit_value_t value) 105 | { 106 | RINFO("set door state: %d", value.uint8_value); 107 | 108 | switch (value.uint8_value) 109 | { 110 | case TGT_OPEN: 111 | open_door(); 112 | break; 113 | case TGT_CLOSED: 114 | close_door(); 115 | break; 116 | default: 117 | ERROR("invalid target door state set requested: %d", value.uint8_value); 118 | break; 119 | } 120 | } 121 | 122 | homekit_value_t obstruction_detected_get() 123 | { 124 | RINFO("get obstruction: %d", garage_door.obstructed); 125 | return HOMEKIT_BOOL_CPP(garage_door.obstructed); 126 | } 127 | 128 | homekit_value_t active_state_get() 129 | { 130 | RINFO("get active: %d", garage_door.active); 131 | return HOMEKIT_BOOL_CPP(garage_door.active); 132 | } 133 | 134 | homekit_value_t current_lock_state_get() 135 | { 136 | RINFO("get current lock state: %d", garage_door.current_lock); 137 | 138 | return HOMEKIT_UINT8_CPP(garage_door.current_lock); 139 | } 140 | 141 | homekit_value_t target_lock_state_get() 142 | { 143 | RINFO("get target lock state: %d", garage_door.target_lock); 144 | 145 | return HOMEKIT_UINT8_CPP(garage_door.target_lock); 146 | } 147 | 148 | void target_lock_state_set(const homekit_value_t value) 149 | { 150 | RINFO("set lock state: %d", value.uint8_value); 151 | 152 | set_lock(value.uint8_value); 153 | } 154 | 155 | void notify_homekit_target_door_state_change() 156 | { 157 | if (arduino_homekit_get_running_server()) 158 | { 159 | homekit_characteristic_notify( 160 | &target_door_state, 161 | HOMEKIT_UINT8_CPP(garage_door.target_state)); 162 | } 163 | } 164 | 165 | void notify_homekit_current_door_state_change() 166 | { 167 | if (arduino_homekit_get_running_server()) 168 | { 169 | homekit_characteristic_notify( 170 | ¤t_door_state, 171 | HOMEKIT_UINT8_CPP(garage_door.current_state)); 172 | } 173 | } 174 | 175 | void notify_homekit_active() 176 | { 177 | if (arduino_homekit_get_running_server()) 178 | { 179 | homekit_characteristic_notify( 180 | &active_state, 181 | HOMEKIT_BOOL_CPP(true)); 182 | } 183 | } 184 | 185 | homekit_value_t light_state_get() 186 | { 187 | RINFO("get light state: %s", garage_door.light ? "On" : "Off"); 188 | 189 | return HOMEKIT_BOOL_CPP(garage_door.light); 190 | } 191 | 192 | void light_state_set(const homekit_value_t value) 193 | { 194 | RINFO("set light: %s", value.bool_value ? "On" : "Off"); 195 | 196 | set_light(value.bool_value); 197 | } 198 | 199 | void notify_homekit_obstruction() 200 | { 201 | if (arduino_homekit_get_running_server()) 202 | { 203 | homekit_characteristic_notify( 204 | &obstruction_detected, 205 | HOMEKIT_BOOL_CPP(garage_door.obstructed)); 206 | } 207 | } 208 | 209 | void notify_homekit_current_lock() 210 | { 211 | if (arduino_homekit_get_running_server()) 212 | { 213 | homekit_characteristic_notify( 214 | ¤t_lock_state, 215 | HOMEKIT_UINT8_CPP(garage_door.current_lock)); 216 | } 217 | } 218 | 219 | void notify_homekit_target_lock() 220 | { 221 | if (arduino_homekit_get_running_server()) 222 | { 223 | homekit_characteristic_notify( 224 | &target_lock_state, 225 | HOMEKIT_UINT8_CPP(garage_door.target_lock)); 226 | } 227 | } 228 | 229 | void notify_homekit_light() 230 | { 231 | if (arduino_homekit_get_running_server()) 232 | { 233 | homekit_characteristic_notify( 234 | &light_state, 235 | HOMEKIT_BOOL_CPP(garage_door.light)); 236 | } 237 | } 238 | 239 | void enable_service_homekit_motion(bool reboot) 240 | { 241 | write_int_to_file("has_motion", 1); 242 | if (reboot) 243 | { 244 | sync_and_restart(); 245 | } 246 | } 247 | 248 | void notify_homekit_motion() 249 | { 250 | if (arduino_homekit_get_running_server()) 251 | { 252 | homekit_characteristic_notify( 253 | &motion_detected, 254 | HOMEKIT_BOOL_CPP(garage_door.motion)); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/homekit.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Brandon Matthews 2 | // All rights reserved. GPLv3 License 3 | 4 | void setup_homekit(); 5 | 6 | void homekit_loop(); 7 | 8 | void notify_homekit_target_door_state_change(); 9 | void notify_homekit_current_door_state_change(); 10 | void notify_homekit_active(); 11 | void notify_homekit_target_lock(); 12 | void notify_homekit_current_lock(); 13 | void notify_homekit_obstruction(); 14 | void notify_homekit_light(); 15 | void enable_service_homekit_motion(bool); 16 | void notify_homekit_motion(); 17 | -------------------------------------------------------------------------------- /src/homekit_decl.c: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Brandon Matthews 2 | // All rights reserved. GPLv3 License 3 | 4 | /* 5 | * HomeKit accessory, service, and characteristic declarations and definitions. 6 | * 7 | * These are in a C file and included via `extern "C"` from the C++ sources in 8 | * order to make their declarations a bit easier to write and understand. 9 | * 10 | * TODO add a service for the light 11 | * TODO add a service for the motion detector 12 | */ 13 | 14 | #include 15 | #include 16 | 17 | #include "homekit_decl.h" 18 | 19 | // Called to identify this accessory (HAP section 6.7.6 Identify Routine) 20 | // 21 | // Called when paired successfully or after clicking the "Identify Accessory" 22 | // button in Home app. 23 | void identify(homekit_value_t _value) 24 | { 25 | printf("accessory identify\n"); 26 | } 27 | 28 | char device_name[DEVICE_NAME_SIZE] = ""; 29 | char device_name_rfc952[DEVICE_NAME_SIZE] = ""; 30 | char serial_number[SERIAL_NAME_SIZE] = ""; 31 | 32 | homekit_characteristic_t active_state = HOMEKIT_CHARACTERISTIC_( 33 | STATUS_ACTIVE, false, ); 34 | 35 | homekit_characteristic_t current_door_state = HOMEKIT_CHARACTERISTIC_( 36 | CURRENT_DOOR_STATE, HOMEKIT_CHARACTERISTIC_CURRENT_DOOR_STATE_CLOSED, 37 | //.getter=current_door_state_get, 38 | //.setter=NULL 39 | ); 40 | 41 | homekit_characteristic_t target_door_state = HOMEKIT_CHARACTERISTIC_( 42 | TARGET_DOOR_STATE, HOMEKIT_CHARACTERISTIC_TARGET_DOOR_STATE_CLOSED, 43 | //.getter=target_door_state_get, 44 | //.setter=target_door_state_set 45 | ); 46 | 47 | homekit_characteristic_t obstruction_detected = HOMEKIT_CHARACTERISTIC_( 48 | OBSTRUCTION_DETECTED, false, 49 | //.getter=obstruction_detected_get, 50 | //.setter=NULL 51 | ); 52 | 53 | homekit_characteristic_t current_lock_state = HOMEKIT_CHARACTERISTIC_( 54 | LOCK_CURRENT_STATE, HOMEKIT_CHARACTERISTIC_CURRENT_LOCK_STATE_UNSECURED, ); 55 | 56 | homekit_characteristic_t target_lock_state = HOMEKIT_CHARACTERISTIC_( 57 | LOCK_TARGET_STATE, HOMEKIT_CHARACTERISTIC_TARGET_LOCK_STATE_UNSECURED, ); 58 | 59 | homekit_characteristic_t light_state = HOMEKIT_CHARACTERISTIC_( 60 | ON, false, ); 61 | 62 | homekit_characteristic_t motion_detected = HOMEKIT_CHARACTERISTIC_( 63 | MOTION_DETECTED, false, ); 64 | 65 | // Declare and define the accessory 66 | homekit_accessory_t *accessories[] = { 67 | HOMEKIT_ACCESSORY(.id = 1, .category = homekit_accessory_category_garage, .services = (homekit_service_t *[]){ 68 | HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics = (homekit_characteristic_t *[]) 69 | { 70 | HOMEKIT_CHARACTERISTIC(NAME, device_name_rfc952), 71 | HOMEKIT_CHARACTERISTIC(MANUFACTURER, "ratCloud llc"), 72 | HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, serial_number), 73 | HOMEKIT_CHARACTERISTIC(MODEL, "ratgdo"), 74 | HOMEKIT_CHARACTERISTIC(FIRMWARE_REVISION, AUTO_VERSION), 75 | HOMEKIT_CHARACTERISTIC(IDENTIFY, identify), 76 | NULL 77 | }), 78 | HOMEKIT_SERVICE(GARAGE_DOOR_OPENER, .primary = true, .characteristics = (homekit_characteristic_t *[]) 79 | { 80 | HOMEKIT_CHARACTERISTIC(NAME, "ratgdo"), 81 | &active_state, 82 | ¤t_door_state, 83 | &target_door_state, 84 | &obstruction_detected, 85 | ¤t_lock_state, 86 | &target_lock_state, 87 | NULL 88 | }), 89 | HOMEKIT_SERVICE(LIGHTBULB, .primary = false, .characteristics = (homekit_characteristic_t *[]) 90 | { 91 | HOMEKIT_CHARACTERISTIC(NAME, "ratgdo"), 92 | &light_state, 93 | NULL 94 | }), 95 | HOMEKIT_SERVICE(MOTION_SENSOR, .primary = false, .characteristics = (homekit_characteristic_t *[]) 96 | { 97 | HOMEKIT_CHARACTERISTIC(NAME, "ratgdo"), 98 | &motion_detected, 99 | NULL 100 | }), 101 | NULL 102 | }), 103 | NULL 104 | }; 105 | 106 | // Overall HomeKit server config 107 | homekit_server_config_t config = { 108 | .accessories = accessories, 109 | .password = "251-02-023", // On Oct 25, 2023, Chamberlain announced they were disabling API 110 | // access for "unauthorized" third parties. 111 | .setupId = "RTGO", 112 | }; 113 | -------------------------------------------------------------------------------- /src/homekit_decl.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Brandon Matthews 2 | // All rights reserved. GPLv3 License 3 | 4 | #ifndef _HOMEKIT_DECL_H 5 | #define _HOMEKIT_DECL_H 6 | 7 | #define DEVICE_NAME_SIZE 32 8 | #define SERIAL_NAME_SIZE 18 9 | 10 | extern char device_name[DEVICE_NAME_SIZE]; 11 | extern char device_name_rfc952[DEVICE_NAME_SIZE]; 12 | 13 | // Possible values for characteristic CURRENT_DOOR_STATE: 14 | #define HOMEKIT_CHARACTERISTIC_CURRENT_DOOR_STATE_OPEN 0 15 | #define HOMEKIT_CHARACTERISTIC_CURRENT_DOOR_STATE_CLOSED 1 16 | #define HOMEKIT_CHARACTERISTIC_CURRENT_DOOR_STATE_OPENING 2 17 | #define HOMEKIT_CHARACTERISTIC_CURRENT_DOOR_STATE_CLOSING 3 18 | #define HOMEKIT_CHARACTERISTIC_CURRENT_DOOR_STATE_STOPPED 4 19 | 20 | #define HOMEKIT_CHARACTERISTIC_TARGET_DOOR_STATE_OPEN 0 21 | #define HOMEKIT_CHARACTERISTIC_TARGET_DOOR_STATE_CLOSED 1 22 | 23 | #define HOMEKIT_CHARACTERISTIC_CURRENT_LOCK_STATE_UNSECURED 0 24 | #define HOMEKIT_CHARACTERISTIC_CURRENT_LOCK_STATE_SECURED 1 25 | #define HOMEKIT_CHARACTERISTIC_CURRENT_LOCK_STATE_JAMMED 2 26 | #define HOMEKIT_CHARACTERISTIC_CURRENT_LOCK_STATE_UNKNOWN 3 27 | 28 | #define HOMEKIT_CHARACTERISTIC_TARGET_LOCK_STATE_UNSECURED 0 29 | #define HOMEKIT_CHARACTERISTIC_TARGET_LOCK_STATE_SECURED 1 30 | 31 | #endif // _HOMEKIT_DECL_H 32 | -------------------------------------------------------------------------------- /src/log.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Brandon Matthews 2 | // All rights reserved. GPLv3 License 3 | 4 | #include 5 | #include 6 | 7 | #include "log.h" 8 | #include "utilities.h" 9 | #include "secplus2.h" 10 | #include "comms.h" 11 | #include "web.h" 12 | 13 | #ifndef UNIT_TEST 14 | 15 | #include 16 | 17 | void print_packet(uint8_t pkt[SECPLUS2_CODE_LEN]) 18 | { 19 | RINFO("decoded packet: [%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X]", 20 | pkt[0], pkt[1], pkt[2], pkt[3], pkt[4], pkt[5], pkt[6], pkt[7], pkt[8], pkt[9], 21 | pkt[10], pkt[11], pkt[12], pkt[13], pkt[14], pkt[15], pkt[16], pkt[17], pkt[18]); 22 | } 23 | 24 | #else // UNIT_TEST 25 | 26 | void print_packet(uint8_t pkt[SECPLUS2_CODE_LEN]) {} 27 | 28 | #endif // UNIT_TEST 29 | 30 | #ifdef LOG_MSG_BUFFER 31 | #define LINE_BUFFER_SIZE 256 32 | char *lineBuffer = NULL; 33 | logBuffer *msgBuffer = NULL; // Buffer to save log messages as they occur 34 | File logMessageFile; 35 | 36 | #define SYSLOG_LOCAL0 16 37 | #define SYSLOG_EMERGENCY 0 38 | #define SYSLOG_ALERT 1 39 | #define SYSLOG_CRIT 2 40 | #define SYSLOG_ERROR 3 41 | #define SYSLOG_WARN 4 42 | #define SYSLOG_NOTICE 5 43 | #define SYSLOG_INFO 6 44 | #define SYSLOG_DEBUG 7 45 | #define SYSLOG_NIL "-" 46 | #define SYSLOG_BOM "\xEF\xBB\xBF" 47 | 48 | WiFiUDP syslog; 49 | void logToSyslog(char *message) 50 | { 51 | if (!syslogEn || !WiFi.isConnected()) 52 | return; 53 | 54 | uint8_t PRI = SYSLOG_LOCAL0 * 8; 55 | if (*message == '>') 56 | PRI += SYSLOG_INFO; 57 | else if (*message == '!') 58 | PRI += SYSLOG_ERROR; 59 | 60 | char *app_name; 61 | char *msg; 62 | 63 | app_name = strtok(message, "]"); 64 | while (*app_name == ' ') 65 | app_name++; 66 | app_name = strtok(NULL, ":"); 67 | while (*app_name == ' ') 68 | app_name++; 69 | msg = strtok(NULL, "\r\n"); 70 | while (*msg == ' ') 71 | msg++; 72 | 73 | syslog.beginPacket(userConfig->syslogIP, userConfig->syslogPort); 74 | 75 | // Use RFC5424 Format 76 | syslog.printf("<%u>1 ", PRI); // PRI code 77 | #if defined(NTP_CLIENT) && defined(USE_NTP_TIMESTAMP) 78 | syslog.print((enableNTP && clockSet) ? timeString(0, true) : SYSLOG_NIL); 79 | #else 80 | syslog.print(SYSLOG_NIL); // Time - let the syslog server insert time 81 | #endif 82 | syslog.print(" "); 83 | syslog.print(device_name_rfc952); // hostname 84 | syslog.print(" "); 85 | syslog.print(app_name); // application name 86 | syslog.printf(" %d", loop_id); // process ID 87 | syslog.print(" " SYSLOG_NIL // message ID 88 | " " SYSLOG_NIL // structured data 89 | #ifdef USE_UTF8_BOM 90 | " " SYSLOG_BOM); // BOM - indicates UTF-8 encoding 91 | #else 92 | " " ); // No BOM 93 | #endif 94 | syslog.print(msg); // message 95 | 96 | syslog.endPacket(); 97 | } 98 | 99 | void logToBuffer_P(const char *fmt, ...) 100 | { 101 | if (!msgBuffer) 102 | { 103 | // first time in we need to create the buffers 104 | Serial.printf_P(PSTR("Allocating memory for logs\n")); 105 | IRAM_START 106 | // IRAM heap is used only for allocating globals, to leave as much regular heap 107 | // available during operations. We need to carefully monitor useage so as not 108 | // to exceed available IRAM. We can adjust the LOG_BUFFER_SIZE (in log.h) if we 109 | // need to make more space available for initialization. 110 | #if defined(MMU_IRAM_HEAP) 111 | Serial.printf_P(PSTR("IRAM heap size %d\n"), MMU_SEC_HEAP_SIZE); 112 | #endif 113 | msgBuffer = (logBuffer *)malloc(sizeof(logBuffer)); 114 | Serial.printf_P(PSTR("Allocated %d bytes for message log buffer\n"), sizeof(logBuffer)); 115 | lineBuffer = (char *)malloc(LINE_BUFFER_SIZE); 116 | Serial.printf_P(PSTR("Allocated %d bytes for line buffer\n"), LINE_BUFFER_SIZE); 117 | // Fill the buffer with space chars... because if we crash and dump buffer before it fills 118 | // up, we want blank space not garbage! Nothing is null-terminated in this circular buffer. 119 | memset(msgBuffer->buffer, 0x20, sizeof(msgBuffer->buffer)); 120 | msgBuffer->wrapped = 0; 121 | msgBuffer->head = 0; 122 | // Open logMessageFile so we don't have to later. 123 | logMessageFile = (LittleFS.exists(CRASH_LOG_MSG_FILE)) ? LittleFS.open(CRASH_LOG_MSG_FILE, "r+") : LittleFS.open(CRASH_LOG_MSG_FILE, "w+"); 124 | Serial.printf_P(PSTR("Opened log message file, size: %d\n"), logMessageFile.size()); 125 | IRAM_END("Log buffers allocated"); 126 | } 127 | 128 | // parse the format string into lineBuffer 129 | va_list args; 130 | va_start(args, fmt); 131 | vsnprintf_P(lineBuffer, LINE_BUFFER_SIZE, fmt, args); 132 | va_end(args); 133 | // print line to the serial port 134 | Serial.print(lineBuffer); 135 | // copy the line into the message save buffer 136 | size_t len = strlen(lineBuffer); 137 | size_t available = sizeof(msgBuffer->buffer) - msgBuffer->head; 138 | memcpy(&msgBuffer->buffer[msgBuffer->head], lineBuffer, min(available, len)); 139 | if (available < len) 140 | { 141 | // we wrapped on the available buffer space 142 | msgBuffer->wrapped = 1; 143 | msgBuffer->head = len - available; 144 | memcpy(msgBuffer->buffer, &lineBuffer[available], msgBuffer->head); 145 | } 146 | else 147 | { 148 | msgBuffer->head += len; 149 | } 150 | // send it to subscribed browsers 151 | SSEBroadcastState(lineBuffer, LOG_MESSAGE); 152 | logToSyslog(lineBuffer); 153 | } 154 | 155 | #ifdef ENABLE_CRASH_LOG 156 | void crashCallback() 157 | { 158 | if (msgBuffer && logMessageFile) 159 | { 160 | logMessageFile.truncate(0); 161 | logMessageFile.seek(0, fs::SeekSet); 162 | logMessageFile.println(); 163 | logMessageFile.write(ESP.checkFlashCRC() ? "Flash CRC OK" : "Flash CRC BAD"); 164 | logMessageFile.println(); 165 | printMessageLog(logMessageFile); 166 | logMessageFile.close(); 167 | } 168 | // We may not have enough memory to open the file and save the code 169 | // save_rolling_code(); 170 | } 171 | #endif 172 | 173 | void printSavedLog(File file, Print &outputDev) 174 | { 175 | if (file && file.size() > 0) 176 | { 177 | int num = LINE_BUFFER_SIZE; 178 | file.seek(0, fs::SeekSet); 179 | while (num == LINE_BUFFER_SIZE) 180 | { 181 | num = file.read((uint8_t *)lineBuffer, LINE_BUFFER_SIZE); 182 | outputDev.write(lineBuffer, num); 183 | } 184 | outputDev.println(); 185 | } 186 | } 187 | 188 | void printSavedLog(Print &outputDev) 189 | { 190 | return printSavedLog(logMessageFile, outputDev); 191 | } 192 | 193 | // These are defined in the linker script, and filled in by the elf2bin.py util 194 | extern "C" uint32_t __crc_len; 195 | extern "C" uint32_t __crc_val; 196 | // Memory stats 197 | extern "C" uint32_t free_heap; 198 | extern "C" uint32_t min_heap; 199 | 200 | void printMessageLog(Print &outputDev) 201 | { 202 | #ifdef NTP_CLIENT 203 | if (enableNTP && clockSet) 204 | { 205 | outputDev.write("Server time (secs): "); 206 | outputDev.println(time(NULL)); 207 | } 208 | #endif 209 | outputDev.write("Server uptime (ms): "); 210 | outputDev.println(millis()); 211 | outputDev.write("Firmware version: "); 212 | outputDev.write(AUTO_VERSION); 213 | outputDev.println(); 214 | outputDev.write("Flash CRC: 0x"); 215 | outputDev.println(__crc_val, 16); 216 | outputDev.write("Flash length: "); 217 | outputDev.println(__crc_len); 218 | outputDev.write("Free heap: "); 219 | outputDev.println(free_heap); 220 | outputDev.write("Minimum heap: "); 221 | outputDev.println(min_heap); 222 | #if defined(MMU_IRAM_HEAP) 223 | outputDev.write("IRAM heap size: "); 224 | outputDev.println(MMU_SEC_HEAP_SIZE); 225 | #endif 226 | outputDev.println(); 227 | if (msgBuffer) 228 | { 229 | if (msgBuffer->wrapped != 0) 230 | { 231 | outputDev.write(&msgBuffer->buffer[msgBuffer->head], sizeof(msgBuffer->buffer) - msgBuffer->head); 232 | } 233 | outputDev.write(msgBuffer->buffer, msgBuffer->head); 234 | } 235 | } 236 | #endif // LOG_MSG_BUFFER 237 | -------------------------------------------------------------------------------- /src/ratgdo.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "ratgdo.h" 6 | #include "wifi.h" 7 | #include "homekit.h" 8 | #include "comms.h" 9 | #include "log.h" 10 | #include "web.h" 11 | #include "utilities.h" 12 | #include "Packet.h" 13 | 14 | #include 15 | #include 16 | 17 | time_t now = 0; 18 | tm timeInfo; 19 | 20 | void showTime() 21 | { 22 | localtime_r(&now, &timeInfo); // update the structure tm with the current time 23 | Serial.print("year:"); 24 | Serial.print(timeInfo.tm_year + 1900); // years since 1900 25 | Serial.print("\tmonth:"); 26 | Serial.print(timeInfo.tm_mon + 1); // January = 0 (!) 27 | Serial.print("\tday:"); 28 | Serial.print(timeInfo.tm_mday); // day of month 29 | Serial.print("\thour:"); 30 | Serial.print(timeInfo.tm_hour); // hours since midnight 0-23 31 | Serial.print("\tmin:"); 32 | Serial.print(timeInfo.tm_min); // minutes after the hour 0-59 33 | Serial.print("\tsec:"); 34 | Serial.print(timeInfo.tm_sec); // seconds after the minute 0-61* 35 | Serial.print("\twday"); 36 | Serial.print(timeInfo.tm_wday); // days since Sunday 0-6 37 | if (timeInfo.tm_isdst == 1) // Daylight Saving Time flag 38 | Serial.print("\tDST"); 39 | else 40 | Serial.print("\tstandard"); 41 | Serial.println(); 42 | } 43 | 44 | /********************************* FWD DECLARATIONS *****************************************/ 45 | 46 | void setup_pins(); 47 | void IRAM_ATTR isr_obstruction(); 48 | void service_timer_loop(); 49 | void dryContactLoop(); 50 | void onOpenSwitchPress(); 51 | void onCloseSwitchPress(); 52 | void onOpenSwitchRelease(); 53 | void onCloseSwitchRelease(); 54 | 55 | //Define OneButton objects for open/close pins 56 | OneButton buttonOpen(DRY_CONTACT_OPEN_PIN, true, true); // Active low, with internal pull-up 57 | OneButton buttonClose(DRY_CONTACT_CLOSE_PIN, true, true); 58 | bool dryContactDoorOpen = false; 59 | bool dryContactDoorClose = false; 60 | bool previousDryContactDoorOpen = false; 61 | bool previousDryContactDoorClose = false; 62 | 63 | /********************************* RUNTIME STORAGE *****************************************/ 64 | 65 | struct obstruction_sensor_t 66 | { 67 | unsigned int low_count = 0; // count obstruction low pulses 68 | unsigned long last_asleep = 0; // count time between high pulses from the obst ISR 69 | } obstruction_sensor; 70 | 71 | // long unsigned int led_reset_time = 0; // Stores time when LED should return to idle state 72 | // uint8_t led_active_state = LOW; // LOW == LED on, HIGH == LED off 73 | // uint8_t led_idle_state = HIGH; // opposite of active 74 | LED led; 75 | 76 | uint8_t loop_id; 77 | 78 | extern bool flashCRC; 79 | 80 | struct GarageDoor garage_door; 81 | 82 | extern "C" uint32_t __crc_len; 83 | extern "C" uint32_t __crc_val; 84 | 85 | // Track our memory usage 86 | uint32_t free_heap = 65535; 87 | uint32_t min_heap = 65535; 88 | unsigned long next_heap_check = 0; 89 | 90 | bool status_done = false; 91 | unsigned long status_timeout; 92 | 93 | /********************************** MAIN LOOP CODE *****************************************/ 94 | 95 | void setup() 96 | { 97 | disable_extra4k_at_link_time(); 98 | Serial.begin(115200); 99 | flashCRC = ESP.checkFlashCRC(); 100 | LittleFS.begin(); 101 | 102 | Serial.printf("\n"); // newline before we start 103 | led = LED(); 104 | RINFO("=== Starting RATGDO Homekit version %s", AUTO_VERSION); 105 | RINFO("%s", ESP.getFullVersion().c_str()); 106 | RINFO("Flash chip size 0x%X", ESP.getFlashChipSize()); 107 | RINFO("Flash chip mode 0x%X", ESP.getFlashChipMode()); 108 | RINFO("Flash chip speed 0x%X (%d MHz)", ESP.getFlashChipSpeed(), ESP.getFlashChipSpeed() / 1000000); 109 | // CRC checking starts at memory location 0x40200000, and proceeds until the address of __crc_len and __crc_val... 110 | // For CRC calculation purposes, those two long (32 bit) values are assumed to be zero. 111 | // The CRC calculation then proceeds until it get to 0x4020000 plus __crc_len. 112 | // Any memory writes/corruption within these blocks will cause checkFlashCRC() to fail. 113 | RINFO("Firmware CRC value: 0x%08X, CRC length: 0x%X (%d), Memory address of __crc_len,__crc_val: 0x%08X,0x%08X", __crc_val, __crc_len, __crc_len, &__crc_len, &__crc_val); 114 | if (flashCRC) 115 | { 116 | RINFO("checkFlashCRC: true"); 117 | } 118 | else 119 | { 120 | RERROR("checkFlashCRC: false"); 121 | } 122 | load_all_config_settings(); 123 | wifi_connect(); 124 | setup_web(); 125 | if (!softAPmode) 126 | { 127 | setup_pins(); 128 | setup_comms(); 129 | setup_homekit(); 130 | } 131 | 132 | led.idle(); 133 | RINFO("=== RATGDO setup complete ==="); 134 | RINFO("============================="); 135 | status_timeout = millis() + 2000; 136 | } 137 | 138 | void loop() 139 | { 140 | improv_loop(); 141 | comms_loop(); 142 | // Poll OneButton objects 143 | buttonOpen.tick(); 144 | buttonClose.tick(); 145 | 146 | // wait for a status command to be processes to properly set the initial state of 147 | // all homekit characteristics. Also timeout if we don't receive a status in 148 | // a reasonable amount of time. This prevents unintentional state changes if 149 | // a home hub reads the state before we initialize everything 150 | // Note, secplus1 doesnt have a status command so it will just timeout 151 | if (status_done) 152 | { 153 | homekit_loop(); 154 | } 155 | else if (millis() > status_timeout) 156 | { 157 | RINFO("Status timeout, starting homekit"); 158 | status_done = true; 159 | } 160 | service_timer_loop(); 161 | web_loop(); 162 | dryContactLoop(); 163 | loop_id = LOOP_SYSTEM; 164 | } 165 | 166 | /*********************************** HELPER FUNCTIONS **************************************/ 167 | 168 | void setup_pins() 169 | { 170 | RINFO("Setting up pins"); 171 | 172 | pinMode(UART_TX_PIN, OUTPUT); 173 | pinMode(UART_RX_PIN, INPUT_PULLUP); 174 | 175 | pinMode(INPUT_OBST_PIN, INPUT); 176 | 177 | 178 | pinMode(STATUS_DOOR_PIN, OUTPUT); 179 | 180 | pinMode(STATUS_OBST_PIN, OUTPUT); 181 | 182 | pinMode(DRY_CONTACT_OPEN_PIN, INPUT_PULLUP); 183 | pinMode(DRY_CONTACT_CLOSE_PIN, INPUT_PULLUP); 184 | 185 | // Attach OneButton handlers 186 | buttonOpen.attachPress(onOpenSwitchPress); 187 | buttonClose.attachPress(onCloseSwitchPress); 188 | buttonOpen.attachLongPressStop(onOpenSwitchRelease); 189 | buttonClose.attachLongPressStop(onCloseSwitchRelease); 190 | /* pin-based obstruction detection 191 | // FALLING from https://github.com/ratgdo/esphome-ratgdo/blob/e248c705c5342e99201de272cb3e6dc0607a0f84/components/ratgdo/ratgdo.cpp#L54C14-L54C14 192 | */ 193 | attachInterrupt(INPUT_OBST_PIN, isr_obstruction, FALLING); 194 | } 195 | 196 | /*********************************** MODEL **************************************/ 197 | 198 | /*************************** DRY CONTACT CONTROL OF LIGHT & DOOR ***************************/ 199 | 200 | //Functions for sensing GDO open/closed 201 | void onOpenSwitchPress() { 202 | dryContactDoorOpen = true; 203 | RINFO("Open switch pressed"); 204 | } 205 | 206 | void onCloseSwitchPress() { 207 | dryContactDoorClose = true; 208 | RINFO("Close switch pressed"); 209 | } 210 | 211 | void onOpenSwitchRelease() { 212 | dryContactDoorOpen = false; 213 | RINFO("Open switch released"); 214 | } 215 | 216 | void onCloseSwitchRelease() { 217 | dryContactDoorClose = false; 218 | RINFO("Close switch released"); 219 | } 220 | 221 | // handle changes to the dry contact state 222 | void dryContactLoop(){ 223 | 224 | if(dryContactDoorOpen){ 225 | if(userConfig->gdoSecurityType == 3){ 226 | doorState = DoorState::Open; 227 | }else{ 228 | Serial.println("Dry Contact: open the door"); 229 | open_door(); 230 | dryContactDoorOpen = false; 231 | } 232 | } 233 | 234 | if(dryContactDoorClose){ 235 | if(userConfig->gdoSecurityType == 3){ 236 | doorState = DoorState::Closed; 237 | }else{ 238 | Serial.println("Dry Contact: close the door"); 239 | close_door(); 240 | dryContactDoorClose = false; 241 | } 242 | } 243 | 244 | if(userConfig->gdoSecurityType == 3){ 245 | if(!dryContactDoorClose && !dryContactDoorOpen){ 246 | if(previousDryContactDoorClose){ 247 | doorState = DoorState::Opening; 248 | } else if (previousDryContactDoorOpen){ 249 | doorState = DoorState::Closing; 250 | } 251 | } 252 | 253 | if(previousDryContactDoorOpen != dryContactDoorOpen){ 254 | previousDryContactDoorOpen = dryContactDoorOpen; 255 | } 256 | if(previousDryContactDoorClose != dryContactDoorClose){ 257 | previousDryContactDoorClose = dryContactDoorClose; 258 | } 259 | } 260 | } 261 | 262 | /*************************** OBSTRUCTION DETECTION ***************************/ 263 | void IRAM_ATTR isr_obstruction() 264 | { 265 | obstruction_sensor.low_count++; 266 | } 267 | 268 | void obstruction_timer() 269 | { 270 | unsigned long current_millis = millis(); 271 | static unsigned long last_millis = 0; 272 | 273 | // the obstruction sensor has 3 states: clear (HIGH with LOW pulse every 7ms), obstructed (HIGH), asleep (LOW) 274 | // the transitions between awake and asleep are tricky because the voltage drops slowly when falling asleep 275 | // and is high without pulses when waking up 276 | 277 | // If at least 3 low pulses are counted within 50ms, the door is awake, not obstructed and we don't have to check anything else 278 | 279 | const long CHECK_PERIOD = 50; 280 | const long PULSES_LOWER_LIMIT = 3; 281 | if (current_millis - last_millis > CHECK_PERIOD) 282 | { 283 | // check to see if we got more then PULSES_LOWER_LIMIT pulses 284 | if (obstruction_sensor.low_count > PULSES_LOWER_LIMIT) 285 | { 286 | // Only update if we are changing state 287 | if (garage_door.obstructed) 288 | { 289 | RINFO("Obstruction Clear"); 290 | garage_door.obstructed = false; 291 | notify_homekit_obstruction(); 292 | digitalWrite(STATUS_OBST_PIN, garage_door.obstructed); 293 | if (motionTriggers.bit.obstruction) 294 | { 295 | garage_door.motion = false; 296 | notify_homekit_motion(); 297 | } 298 | } 299 | } 300 | else if (obstruction_sensor.low_count == 0) 301 | { 302 | // if there have been no pulses the line is steady high or low 303 | if (!digitalRead(INPUT_OBST_PIN)) 304 | { 305 | // asleep 306 | obstruction_sensor.last_asleep = current_millis; 307 | } 308 | else 309 | { 310 | // if the line is high and was last asleep more than 700ms ago, then there is an obstruction present 311 | if (current_millis - obstruction_sensor.last_asleep > 700) 312 | { 313 | // Only update if we are changing state 314 | if (!garage_door.obstructed) 315 | { 316 | RINFO("Obstruction Detected"); 317 | garage_door.obstructed = true; 318 | notify_homekit_obstruction(); 319 | digitalWrite(STATUS_OBST_PIN, garage_door.obstructed); 320 | if (motionTriggers.bit.obstruction) 321 | { 322 | garage_door.motion = true; 323 | notify_homekit_motion(); 324 | } 325 | } 326 | } 327 | } 328 | } 329 | 330 | last_millis = current_millis; 331 | obstruction_sensor.low_count = 0; 332 | } 333 | } 334 | 335 | void service_timer_loop() 336 | { 337 | loop_id = LOOP_TIMER; 338 | // Service the Obstruction Timer 339 | obstruction_timer(); 340 | 341 | unsigned long current_millis = millis(); 342 | 343 | #ifdef NTP_CLIENT 344 | if (enableNTP && clockSet && lastRebootAt == 0) 345 | { 346 | lastRebootAt = time(NULL) - (current_millis / 1000); 347 | RINFO("System boot time: %s", timeString(lastRebootAt)); 348 | } 349 | #endif 350 | 351 | // LED flash timer 352 | led.flash(); 353 | 354 | // Motion Clear Timer 355 | if (garage_door.motion && (current_millis > garage_door.motion_timer)) 356 | { 357 | RINFO("Motion Cleared"); 358 | garage_door.motion = false; 359 | notify_homekit_motion(); 360 | } 361 | 362 | // Check heap 363 | if (current_millis > next_heap_check) 364 | { 365 | next_heap_check = current_millis + 1000; 366 | free_heap = ESP.getFreeHeap(); 367 | if (free_heap < min_heap) 368 | { 369 | min_heap = free_heap; 370 | RINFO("Free heap dropped to %d", min_heap); 371 | } 372 | } 373 | } 374 | 375 | // Constructor for LED class 376 | LED::LED() 377 | { 378 | if (UART_TX_PIN != LED_BUILTIN) 379 | { 380 | // Serial.printf("Enabling built-in LED object\n"); 381 | pinMode(LED_BUILTIN, OUTPUT); 382 | on(); 383 | } 384 | } 385 | 386 | void LED::on() 387 | { 388 | digitalWrite(LED_BUILTIN, 0); 389 | } 390 | 391 | void LED::off() 392 | { 393 | digitalWrite(LED_BUILTIN, 1); 394 | } 395 | 396 | void LED::idle() 397 | { 398 | digitalWrite(LED_BUILTIN, idleState); 399 | } 400 | 401 | void LED::setIdleState(uint8_t state) 402 | { 403 | // 0 = LED flashes off (idle is on) 404 | // 1 = LED flashes on (idle is off) 405 | // 3 = LED disabled (active and idle both off) 406 | if (state == 2) 407 | { 408 | idleState = activeState = 1; 409 | } 410 | else 411 | { 412 | idleState = state; 413 | activeState = (state == 1) ? 0 : 1; 414 | } 415 | } 416 | 417 | void LED::flash(unsigned long ms) 418 | { 419 | if (ms) 420 | { 421 | digitalWrite(LED_BUILTIN, activeState); 422 | resetTime = millis() + ms; 423 | } 424 | else if ((digitalRead(LED_BUILTIN) == activeState) && (millis() > resetTime)) 425 | { 426 | digitalWrite(LED_BUILTIN, idleState); 427 | } 428 | } -------------------------------------------------------------------------------- /src/ratgdo.h: -------------------------------------------------------------------------------- 1 | #ifndef _RATGDO_H 2 | #define _RATGDO_H 3 | 4 | #include 5 | #include 6 | #include "homekit_decl.h" 7 | 8 | #define DEVICE_NAME "homekit-ratgdo" 9 | #define MANUF_NAME "ratCloud llc" 10 | #define SERIAL_NUMBER "0P3ND00R" 11 | #define MODEL_NAME "ratgdo_v2.5" 12 | #define CHIP_FAMILY "ESP8266" 13 | 14 | /********************************** PIN DEFINITIONS *****************************************/ 15 | 16 | #define UART_TX_PIN D1 // red control terminal / GarageDoorOpener (UART1 TX) 17 | #define UART_RX_PIN D2 // red control terminal / GarageDoorOpener (UART1 RX) 18 | 19 | #define INPUT_OBST_PIN D7 // black obstruction sensor terminal 20 | #define STATUS_OBST_PIN D8 // output for obstruction status, HIGH for obstructed, LOW for clear 21 | #define STATUS_DOOR_PIN D0 // output door status, HIGH for open, LOW for closed 22 | #define DRY_CONTACT_OPEN_PIN D5 // dry contact for open door limit switch 23 | #define DRY_CONTACT_CLOSE_PIN D6 // dry contact for close door limit switch 24 | 25 | /********************************** MODEL *****************************************/ 26 | 27 | enum GarageDoorCurrentState : uint8_t 28 | { 29 | CURR_OPEN = HOMEKIT_CHARACTERISTIC_CURRENT_DOOR_STATE_OPEN, 30 | CURR_CLOSED = HOMEKIT_CHARACTERISTIC_CURRENT_DOOR_STATE_CLOSED, 31 | CURR_OPENING = HOMEKIT_CHARACTERISTIC_CURRENT_DOOR_STATE_OPENING, 32 | CURR_CLOSING = HOMEKIT_CHARACTERISTIC_CURRENT_DOOR_STATE_CLOSING, 33 | CURR_STOPPED = HOMEKIT_CHARACTERISTIC_CURRENT_DOOR_STATE_STOPPED, 34 | }; 35 | 36 | enum GarageDoorTargetState : uint8_t 37 | { 38 | TGT_OPEN = HOMEKIT_CHARACTERISTIC_TARGET_DOOR_STATE_OPEN, 39 | TGT_CLOSED = HOMEKIT_CHARACTERISTIC_TARGET_DOOR_STATE_CLOSED, 40 | }; 41 | 42 | enum LockCurrentState : uint8_t 43 | { 44 | CURR_UNLOCKED = HOMEKIT_CHARACTERISTIC_CURRENT_LOCK_STATE_UNSECURED, 45 | CURR_LOCKED = HOMEKIT_CHARACTERISTIC_CURRENT_LOCK_STATE_SECURED, 46 | CURR_JAMMED = HOMEKIT_CHARACTERISTIC_CURRENT_LOCK_STATE_JAMMED, 47 | CURR_UNKNOWN = HOMEKIT_CHARACTERISTIC_CURRENT_LOCK_STATE_UNKNOWN, 48 | }; 49 | 50 | enum LockTargetState : uint8_t 51 | { 52 | TGT_UNLOCKED = HOMEKIT_CHARACTERISTIC_TARGET_LOCK_STATE_UNSECURED, 53 | TGT_LOCKED = HOMEKIT_CHARACTERISTIC_TARGET_LOCK_STATE_SECURED, 54 | }; 55 | 56 | struct GarageDoor 57 | { 58 | bool active; 59 | GarageDoorCurrentState current_state; 60 | GarageDoorTargetState target_state; 61 | bool obstructed; 62 | bool has_motion_sensor; 63 | unsigned long motion_timer; 64 | bool motion; 65 | bool light; 66 | LockCurrentState current_lock; 67 | LockTargetState target_lock; 68 | }; 69 | 70 | struct ForceRecover 71 | { 72 | uint8_t push_count; 73 | unsigned long timeout; 74 | }; 75 | 76 | class LED 77 | { 78 | private: 79 | uint8_t activeState = 0; 80 | uint8_t idleState = 1; // opposite of active 81 | unsigned long resetTime = 0; // Stores time when LED should return to idle state 82 | bool initialized = false; 83 | bool enabled = true; 84 | 85 | public: 86 | LED(); 87 | 88 | void on(); 89 | void off(); 90 | void idle(); 91 | void flash(unsigned long ms = 0); 92 | void setIdleState(uint8_t state); 93 | uint8_t getIdleState() { return (idleState == activeState) ? 2 : idleState; }; 94 | }; 95 | 96 | extern LED led; 97 | 98 | #define LOOP_SYSTEM 0 99 | #define LOOP_IMPROV 1 100 | #define LOOP_COMMS 2 101 | #define LOOP_HK 3 102 | #define LOOP_TIMER 4 103 | #define LOOP_WEB 5 104 | extern uint8_t loop_id; 105 | 106 | #define FLASH_MS 50 107 | 108 | #endif // _RATGDO_H 109 | -------------------------------------------------------------------------------- /src/utilities.h: -------------------------------------------------------------------------------- 1 | #ifndef _UTILITIES_H 2 | #define _UTILITIES_H 3 | #include 4 | #include 5 | #include "homekit_decl.h" 6 | #include "ratgdo.h" 7 | 8 | #ifdef NTP_CLIENT 9 | extern bool clockSet; 10 | extern unsigned long lastRebootAt; 11 | extern char *timeString(time_t reqTime = 0, bool syslog = false); 12 | extern bool enableNTP; 13 | #define NTP_SERVER "pool.ntp.org" 14 | #endif 15 | 16 | #if defined(MMU_IRAM_HEAP) 17 | // IRAM heap is used only for allocating globals, to leave as much regular heap 18 | // available during operations. We need to carefully monitor useage so as not 19 | // to exceed available IRAM. We can adjust the LOG_BUFFER_SIZE (in log.h) if we 20 | // need to make more space available for initialization. 21 | #include 22 | #include 23 | #define IRAM_START \ 24 | { \ 25 | HeapSelectIram ephemeral; 26 | #define IRAM_END(location) \ 27 | RINFO("Free IRAM heap (%s): %d", location, ESP.getFreeHeap()); \ 28 | } 29 | #else 30 | #define IRAM_START { 31 | #define IRAM_END(location) \ 32 | RINFO("Free heap (%s): %d", location, ESP.getFreeHeap()); \ 33 | } 34 | #endif 35 | 36 | // Controls whether to log to syslog server 37 | extern bool syslogEn; 38 | 39 | // Controls soft Access Point mode. 40 | extern bool softAPmode; 41 | 42 | // Password and credential management for HTTP server... 43 | extern const char www_realm[]; 44 | 45 | // struct to hold user configuration settings. 46 | #define IP_ADDRESS_SIZE 16 47 | typedef struct 48 | { 49 | char deviceName[DEVICE_NAME_SIZE]; 50 | bool wifiSettingsChanged = true; 51 | int wifiPower = 20; 52 | int wifiPhyMode = 0; 53 | bool staticIP = false; 54 | char IPaddress[IP_ADDRESS_SIZE] = "0.0.0.0"; 55 | char IPnetmask[IP_ADDRESS_SIZE] = "0.0.0.0"; 56 | char IPgateway[IP_ADDRESS_SIZE] = "0.0.0.0"; 57 | char IPnameserver[IP_ADDRESS_SIZE] = "0.0.0.0"; 58 | bool wwwPWrequired = false; 59 | char wwwUsername[32] = "admin"; 60 | // Credentials are MD5 Hash... server.credentialHash(username, realm, "password"); 61 | char wwwCredentials[36] = "10d3c00fa1e09696601ef113b99f8a87"; 62 | int gdoSecurityType = 2; 63 | int TTCdelay = 0; 64 | int rebootSeconds = 0; 65 | int ledIdleState = 0; 66 | int motionTriggers = 0; 67 | #ifdef NTP_CLIENT 68 | bool enableNTP = false; 69 | int doorUpdateAt = 0; 70 | // Will contain string of region/city and POSIX code separated by semicolon... 71 | // For example... "America/New_York;EST5EDT,M3.2.0,M11.1.0" 72 | // Current maximum string length is known to be 60 chars (+ null terminator), see JavaScript console log. 73 | char timeZone[64] = ""; 74 | #endif 75 | bool softAPmode = false; 76 | bool syslogEn = false; 77 | char syslogIP[IP_ADDRESS_SIZE] = "0.0.0.0"; 78 | int syslogPort = 514; 79 | } userConfig_t; 80 | extern userConfig_t *userConfig; 81 | 82 | // Bitset that identifies what will trigger the motion sensor 83 | typedef struct 84 | { 85 | uint8_t motion : 1; 86 | uint8_t obstruction : 1; 87 | uint8_t lightKey : 1; 88 | uint8_t doorKey : 1; 89 | uint8_t lockKey : 1; 90 | uint8_t undef : 3; 91 | } motionTriggerBitset; 92 | typedef union 93 | { 94 | motionTriggerBitset bit; 95 | uint8_t asInt; 96 | } motionTriggersUnion; 97 | extern motionTriggersUnion motionTriggers; 98 | 99 | // Function declarations 100 | void load_all_config_settings(); 101 | void sync_and_restart(); 102 | 103 | uint32_t read_int_from_file(const char *filename, uint32_t defaultValue = 0); 104 | void write_int_to_file(const char *filename, uint32_t value); 105 | 106 | char *read_string_from_file(const char *filename, const char *defaultValue, char *buffer, int bufsize); 107 | void write_string_to_file(const char *filename, const char *value); 108 | 109 | bool read_config_from_file(); 110 | void write_config_to_file(); 111 | 112 | void delete_file(const char *filename); 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /src/web.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Brandon Matthews 2 | // All rights reserved. GPLv3 License 3 | 4 | #ifndef _WEB_H 5 | #define _WEB_H 6 | 7 | void setup_web(); 8 | void web_loop(); 9 | 10 | enum BroadcastType : uint8_t 11 | { 12 | RATGDO_STATUS = 1, 13 | LOG_MESSAGE = 2, 14 | }; 15 | void SSEBroadcastState(const char *data, BroadcastType type = RATGDO_STATUS); 16 | 17 | extern "C" int crashCount; // pull in number of times crashed. 18 | 19 | #endif // _WEB_H 20 | -------------------------------------------------------------------------------- /src/wifi.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Brandon Matthews 2 | // All rights reserved. GPLv3 License 3 | 4 | /* WiFi configuration and setup 5 | * 6 | * This approach relies on the ESP8266 Arduino WiFi class storing credentials 7 | * in EEPROM on our behalf, and its ability to continue to attempt to 8 | * reconnect. If there are stored credentials, `WiFi.begin()` will use them. If 9 | * not, Improv will eventually call `WiFi.begin`, and in doing so will store 10 | * the credentials. If connection does not succeed after 2 seconds, the stored 11 | * credentials will be erased. 12 | * 13 | * Portions of this code written by Jonathas Barbosa , and adapted from 14 | * https://github.com/jnthas/improv-wifi-demo 15 | */ 16 | 17 | // #if defined(ESP8266) 18 | #include 19 | #include 20 | // #elif defined(ESP32) 21 | // #include 22 | // #endif 23 | #include "improv.h" 24 | #include 25 | #include "ratgdo.h" 26 | #include "log.h" 27 | #include "utilities.h" 28 | #include "wifi.h" 29 | 30 | // support for changeing WiFi settings 31 | unsigned long wifiConnectTimeout = 0; 32 | // support for scaning WiFi networks 33 | bool wifiNetsCmp(wifiNet_t a, wifiNet_t b) 34 | { 35 | // Sorts first by SSID and then by RSSI so strongest signal first. 36 | return (a.ssid < b.ssid) || ((a.ssid == b.ssid) && (a.rssi > b.rssi)); 37 | } 38 | std::multiset wifiNets(&wifiNetsCmp); 39 | station_config wifiConf; 40 | 41 | #define MAX_ATTEMPTS_WIFI_CONNECTION 30 42 | uint8_t x_buffer[128]; 43 | uint8_t x_position = 0; 44 | 45 | void set_error(improv::Error error); 46 | void send_response(std::vector &response); 47 | void set_state(improv::State state); 48 | void get_available_wifi_networks(); 49 | bool on_command_callback(improv::ImprovCommand cmd); 50 | void on_error_callback(improv::Error err); 51 | 52 | WiFiEventHandler connectedHandler; 53 | WiFiEventHandler disconnectedHandler; 54 | WiFiEventHandler gotIPHandler; 55 | WiFiEventHandler dhcpTimeoutHandler; 56 | 57 | void onConnected(const WiFiEventStationModeConnected &evt) 58 | { 59 | RINFO("WiFi connected SSID: %s, Channel: %d", evt.ssid.c_str(), evt.channel); 60 | } 61 | 62 | void onDisconnected(const WiFiEventStationModeDisconnected &evt) 63 | { 64 | RINFO("WiFi disconnected SSID: %s, BSSID: %02x:%02x:%02x:%02x:%02x:%02x, Reason: %d", evt.ssid.c_str(), 65 | evt.bssid[0], evt.bssid[1], evt.bssid[2], evt.bssid[3], evt.bssid[4], evt.bssid[5], evt.reason); 66 | } 67 | 68 | void onGotIP(const WiFiEventStationModeGotIP &evt) 69 | { 70 | strlcpy(userConfig->IPaddress, evt.ip.toString().c_str(), sizeof(userConfig->IPaddress)); 71 | strlcpy(userConfig->IPnetmask, evt.mask.toString().c_str(), sizeof(userConfig->IPnetmask)); 72 | strlcpy(userConfig->IPgateway, evt.gw.toString().c_str(), sizeof(userConfig->IPgateway)); 73 | strlcpy(userConfig->IPnameserver, (WiFi.dnsIP().isSet()) ? WiFi.dnsIP().toString().c_str() : evt.gw.toString().c_str(), sizeof(userConfig->IPnameserver)); 74 | write_config_to_file(); 75 | RINFO("WiFi Got IP: %s, Mask: %s, Gateway: %s, DNS: %s", userConfig->IPaddress, userConfig->IPnetmask, userConfig->IPgateway, userConfig->IPnameserver); 76 | } 77 | 78 | void onDHCPTimeout() 79 | { 80 | RINFO("WiFi DHCP Timeout"); 81 | } 82 | 83 | void wifi_scan() 84 | { 85 | // scan for networks 86 | RINFO("Scanning for networks..."); 87 | wifiNet_t wifiNet; 88 | wifiNets.clear(); 89 | int nNets = std::min((int)WiFi.scanNetworks(), 127); 90 | RINFO("Found %d networks", nNets); 91 | for (int i = 0; i < nNets; i++) 92 | { 93 | wifiNet.ssid = WiFi.SSID(i); 94 | wifiNet.channel = WiFi.channel(i); 95 | wifiNet.rssi = WiFi.RSSI(i); 96 | memcpy(wifiNet.bssid, WiFi.BSSID(i), sizeof(wifiNet.bssid)); 97 | RINFO("Network: %s (Ch:%d, %ddBm) AP: %s", WiFi.SSID(i).c_str(), WiFi.channel(i), WiFi.RSSI(i), WiFi.BSSIDstr(i).c_str()); 98 | wifiNets.insert(wifiNet); 99 | } 100 | // delete scan from memory 101 | WiFi.scanDelete(); 102 | } 103 | 104 | void wifi_connect() 105 | { 106 | RINFO("=== Initialize WiFi %s", (softAPmode) ? "Soft Access Point" : "Station"); 107 | IRAM_START 108 | // IRAM heap is used only for allocating globals, to leave as much regular heap 109 | // available during operations. We need to carefully monitor useage so as not 110 | // to exceed available IRAM. We can adjust the LOG_BUFFER_SIZE (in log.h) if we 111 | // need to make more space available for initialization. 112 | WiFi.persistent(false); 113 | if (softAPmode) 114 | { 115 | RINFO("Start AP mode for: %s", device_name_rfc952); 116 | bool apStarted = WiFi.softAP(device_name_rfc952); 117 | if (apStarted) 118 | { 119 | RINFO("AP started with IP %s", WiFi.softAPIP().toString().c_str()); 120 | } 121 | else 122 | { 123 | RINFO("Error starting AP mode"); 124 | } 125 | userConfig->wifiSettingsChanged = false; 126 | wifi_scan(); 127 | } 128 | else 129 | { 130 | if (userConfig->wifiSettingsChanged) 131 | { 132 | RINFO("WARNING: WiFi settings changed. Will check for connection after 30 seconds."); 133 | } 134 | switch (userConfig->wifiPhyMode) 135 | { 136 | case WIFI_PHY_MODE_11B: 137 | RINFO("Setting WiFi preference to 802.11b (Wi-Fi 1)"); 138 | break; 139 | case WIFI_PHY_MODE_11G: 140 | RINFO("Setting WiFi preference to 802.11g (Wi-Fi 3)"); 141 | break; 142 | case WIFI_PHY_MODE_11N: 143 | RINFO("Setting WiFi preference to 802.11n (Wi-Fi 4)"); 144 | break; 145 | default: 146 | RINFO("Setting WiFi version preference to automatic"); 147 | } 148 | WiFi.mode(WIFI_STA); 149 | WiFi.setSleepMode(WIFI_NONE_SLEEP); 150 | WiFi.setPhyMode((WiFiPhyMode_t)userConfig->wifiPhyMode); 151 | if (userConfig->wifiPower < 20) 152 | { 153 | // Only set WiFi power if set to less than the maximum 154 | RINFO("Setting WiFi power to %d", userConfig->wifiPower); 155 | WiFi.setOutputPower((float)userConfig->wifiPower); 156 | } 157 | WiFi.setAutoReconnect(true); // don't require explicit attempts to reconnect in the main loop 158 | 159 | RINFO("Set WiFi Host Name: %s", device_name_rfc952); 160 | WiFi.hostname((const char *)device_name_rfc952); 161 | 162 | if (userConfig->staticIP) 163 | { 164 | IPAddress ip; 165 | IPAddress gw; 166 | IPAddress nm; 167 | IPAddress dns; 168 | if (ip.fromString(userConfig->IPaddress) && 169 | gw.fromString(userConfig->IPgateway) && 170 | nm.fromString(userConfig->IPnetmask) && 171 | dns.fromString(userConfig->IPnameserver)) 172 | { 173 | WiFi.config(ip, gw, nm, dns); 174 | } 175 | else 176 | { 177 | RINFO("Failed to set static IP address, error parsing addresses"); 178 | } 179 | } 180 | } 181 | // Set callbacks so we can monitor connection status 182 | connectedHandler = WiFi.onStationModeConnected(&onConnected); 183 | disconnectedHandler = WiFi.onStationModeDisconnected(&onDisconnected); 184 | gotIPHandler = WiFi.onStationModeGotIP(&onGotIP); 185 | dhcpTimeoutHandler = WiFi.onStationModeDHCPTimeout(&onDHCPTimeout); 186 | 187 | wifi_station_get_config_default(&wifiConf); 188 | if (wifiConf.bssid_set) 189 | { 190 | RINFO("Connecting to SSID: %s locked to Access Point: %02x:%02x:%02x:%02x:%02x:%02x, ", wifiConf.ssid, 191 | wifiConf.bssid[0], wifiConf.bssid[1], wifiConf.bssid[2], wifiConf.bssid[3], wifiConf.bssid[4], wifiConf.bssid[5]); 192 | } 193 | else 194 | { 195 | RINFO("Connecting to SSID: %s", wifiConf.ssid); 196 | } 197 | RINFO("Starting WiFi connecting in background"); 198 | wifiConnectTimeout = millis() + 30000; 199 | WiFi.begin(); // use credentials stored in flash 200 | IRAM_END("Wifi initialized"); 201 | } 202 | 203 | void improv_loop() 204 | { 205 | loop_id = LOOP_IMPROV; 206 | #ifdef GW_PING_CHECK 207 | static unsigned long gw_ping_timeout = 10000; 208 | static unsigned long gw_report_interval = 0; 209 | // Once a minute ping the Gateway and log 210 | unsigned long now = millis(); 211 | if (now > gw_ping_timeout) { 212 | gw_ping_timeout = now + 60000; 213 | if (Ping.ping(WiFi.gatewayIP(), 1)) { 214 | int lat = Ping.averageTime(); 215 | // Log success once an hour 216 | if ((now > gw_report_interval) || (lat > 100)) { 217 | gw_report_interval = now + (60 * 60 * 1000); 218 | RINFO("Gateway %s alive %u ms", WiFi.gatewayIP().toString().c_str(), lat); 219 | } 220 | } 221 | else { 222 | RINFO("No response from Gateway %s", WiFi.gatewayIP().toString().c_str()); 223 | } 224 | } 225 | #endif 226 | if (Serial.available() > 0) 227 | { 228 | uint8_t b = Serial.read(); 229 | 230 | if (parse_improv_serial_byte(x_position, b, x_buffer, on_command_callback, on_error_callback)) 231 | { 232 | x_buffer[x_position++] = b; 233 | } 234 | else 235 | { 236 | x_position = 0; 237 | } 238 | } 239 | 240 | if (softAPmode && (millis() > 10 * 60 * 1000)) 241 | { 242 | RINFO("In Soft Access Point mode for over 10 minutes, reboot"); 243 | sync_and_restart(); 244 | return; 245 | } 246 | 247 | if (userConfig->wifiSettingsChanged && (millis() > wifiConnectTimeout)) 248 | { 249 | bool connected = (WiFi.status() == WL_CONNECTED); 250 | RINFO("30 seconds since WiFi settings change, connected to access point: %s", (connected) ? "true" : "false"); 251 | // If not connected, reset to auto. 252 | if (!connected) 253 | { 254 | RINFO("Reset WiFi Power to 20.5 dBm and WiFiPhyMode to: 0"); 255 | userConfig->wifiPower = 20; 256 | userConfig->wifiPhyMode = 0; 257 | WiFi.setOutputPower(20.5); 258 | WiFi.setPhyMode((WiFiPhyMode_t)0); 259 | write_config_to_file(); 260 | // Now try and reconnect... 261 | wifiConnectTimeout = millis() + 30000; 262 | WiFi.reconnect(); 263 | return; 264 | } 265 | else 266 | { 267 | RINFO("Connected, test Gatway IP reachable"); 268 | IPAddress ip; 269 | if (!Ping.ping(WiFi.gatewayIP(), 1)) 270 | { 271 | RINFO("Unable to ping Gateway, reset to DHCP to acquire IP address and reconnect"); 272 | userConfig->staticIP = false; 273 | write_config_to_file(); 274 | IPAddress ip; 275 | ip.fromString("0.0.0.0"); 276 | WiFi.config(ip, ip, ip); 277 | // Now try and reconnect... 278 | wifiConnectTimeout = millis() + 30000; 279 | WiFi.reconnect(); 280 | return; 281 | } else { 282 | RINFO("Gateway %s alive %u ms", WiFi.gatewayIP().toString().c_str(), Ping.averageTime()); 283 | } 284 | } 285 | // reset flag 286 | userConfig->wifiSettingsChanged = false; 287 | write_config_to_file(); 288 | } 289 | } 290 | 291 | bool connect_wifi(const std::string &ssid, const std::string &password) 292 | { 293 | return connect_wifi(ssid, password, NULL); 294 | } 295 | 296 | bool connect_wifi(const std::string &ssid, const std::string &password, const uint8_t *bssid) 297 | { 298 | uint8_t count = 0; 299 | 300 | WiFi.persistent(true); // Set persist to store wifi credentials 301 | WiFi.begin(ssid.c_str(), password.c_str(), 0, bssid); 302 | WiFi.persistent(false); // clear the persist flag so other settings do not get written to flash 303 | 304 | while (WiFi.status() != WL_CONNECTED) 305 | { 306 | delay(500); 307 | yield(); 308 | if (count > MAX_ATTEMPTS_WIFI_CONNECTION) 309 | { 310 | WiFi.disconnect(); 311 | return false; 312 | } 313 | count++; 314 | } 315 | return true; 316 | } 317 | 318 | std::vector get_local_url() 319 | { 320 | return { 321 | // TODO 322 | // URL where user can finish onboarding or use device 323 | // Recommended to use website hosted by device 324 | String("http://" + WiFi.localIP().toString()).c_str()}; 325 | } 326 | 327 | void on_error_callback(improv::Error err) 328 | { 329 | RERROR("improv error: %02X", err); 330 | } 331 | 332 | bool on_command_callback(improv::ImprovCommand cmd) 333 | { 334 | 335 | switch (cmd.command) 336 | { 337 | case improv::Command::GET_CURRENT_STATE: 338 | { 339 | if ((WiFi.status() == WL_CONNECTED)) 340 | { 341 | set_state(improv::State::STATE_PROVISIONED); 342 | std::vector data = improv::build_rpc_response(improv::GET_CURRENT_STATE, get_local_url(), false); 343 | send_response(data); 344 | } 345 | else 346 | { 347 | set_state(improv::State::STATE_AUTHORIZED); 348 | } 349 | 350 | break; 351 | } 352 | 353 | case improv::Command::WIFI_SETTINGS: 354 | { 355 | if (cmd.ssid.length() == 0) 356 | { 357 | set_error(improv::Error::ERROR_INVALID_RPC); 358 | break; 359 | } 360 | 361 | set_state(improv::STATE_PROVISIONING); 362 | 363 | if (connect_wifi(cmd.ssid, cmd.password)) 364 | { 365 | set_state(improv::STATE_PROVISIONED); 366 | std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, get_local_url(), false); 367 | send_response(data); 368 | } 369 | else 370 | { 371 | set_state(improv::STATE_STOPPED); 372 | set_error(improv::Error::ERROR_UNABLE_TO_CONNECT); 373 | } 374 | 375 | break; 376 | } 377 | 378 | case improv::Command::GET_DEVICE_INFO: 379 | { 380 | std::vector infos = { 381 | DEVICE_NAME, 382 | AUTO_VERSION, 383 | CHIP_FAMILY, 384 | MODEL_NAME}; 385 | std::vector data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false); 386 | send_response(data); 387 | break; 388 | } 389 | 390 | case improv::Command::GET_WIFI_NETWORKS: 391 | { 392 | get_available_wifi_networks(); 393 | break; 394 | } 395 | 396 | default: 397 | { 398 | set_error(improv::ERROR_UNKNOWN_RPC); 399 | return false; 400 | } 401 | } 402 | 403 | return true; 404 | } 405 | 406 | void get_available_wifi_networks() 407 | { 408 | int networkNum = WiFi.scanNetworks(); 409 | 410 | int sortedIndicies[networkNum]; 411 | for (int i = 0; i < networkNum; i++) 412 | { 413 | sortedIndicies[i] = i; 414 | } 415 | 416 | // sort networks by RSSI, strongest to weakest 417 | for (int i = 0; i < networkNum; i++) 418 | { 419 | for (int j = i + 1; j < networkNum; j++) 420 | { 421 | if (WiFi.RSSI(sortedIndicies[j]) > WiFi.RSSI(sortedIndicies[i])) 422 | { 423 | std::swap(sortedIndicies[i], sortedIndicies[j]); 424 | } 425 | } 426 | } 427 | 428 | // find duplicates (must be RSSI sorted) 429 | String temp_ssid; 430 | for (int i = 0; i < networkNum; i++) 431 | { 432 | if (sortedIndicies[i] == -1) 433 | continue; // skip duplicate 434 | temp_ssid = WiFi.SSID(sortedIndicies[i]); 435 | for (int j = i + 1; j < networkNum; j++) 436 | { 437 | if (temp_ssid == WiFi.SSID(sortedIndicies[j])) 438 | { 439 | sortedIndicies[j] = -1; // set dupes to -1 to skip later 440 | } 441 | } 442 | } 443 | 444 | for (int id = 0; id < networkNum; ++id) 445 | { 446 | if (sortedIndicies[id] == -1) 447 | continue; // skip duplicate 448 | std::vector data = improv::build_rpc_response( 449 | improv::GET_WIFI_NETWORKS, {WiFi.SSID(sortedIndicies[id]), String(WiFi.RSSI(sortedIndicies[id])), (WiFi.encryptionType(sortedIndicies[id]) == ENC_TYPE_NONE ? "NO" : "YES")}, false); 450 | send_response(data); 451 | delay(1); 452 | } 453 | // final response 454 | std::vector data = 455 | improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector{}, false); 456 | send_response(data); 457 | 458 | // delete scan from memory 459 | WiFi.scanDelete(); 460 | } 461 | 462 | void set_state(improv::State state) 463 | { 464 | 465 | std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'}; 466 | data.resize(11); 467 | data[6] = improv::IMPROV_SERIAL_VERSION; 468 | data[7] = improv::TYPE_CURRENT_STATE; 469 | data[8] = 1; 470 | data[9] = state; 471 | 472 | uint8_t checksum = 0x00; 473 | for (uint8_t d : data) 474 | checksum += d; 475 | data[10] = checksum; 476 | 477 | Serial.write(data.data(), data.size()); 478 | } 479 | 480 | void send_response(std::vector &response) 481 | { 482 | std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'}; 483 | data.resize(9); 484 | data[6] = improv::IMPROV_SERIAL_VERSION; 485 | data[7] = improv::TYPE_RPC_RESPONSE; 486 | data[8] = response.size(); 487 | data.insert(data.end(), response.begin(), response.end()); 488 | 489 | uint8_t checksum = 0x00; 490 | for (uint8_t d : data) 491 | checksum += d; 492 | data.push_back(checksum); 493 | 494 | Serial.write(data.data(), data.size()); 495 | } 496 | 497 | void set_error(improv::Error error) 498 | { 499 | std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'}; 500 | data.resize(11); 501 | data[6] = improv::IMPROV_SERIAL_VERSION; 502 | data[7] = improv::TYPE_ERROR_STATE; 503 | data[8] = 1; 504 | data[9] = error; 505 | 506 | uint8_t checksum = 0x00; 507 | for (uint8_t d : data) 508 | checksum += d; 509 | data[10] = checksum; 510 | 511 | Serial.write(data.data(), data.size()); 512 | } 513 | -------------------------------------------------------------------------------- /src/wifi.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Brandon Matthews 2 | // All rights reserved. GPLv3 License 3 | 4 | #ifndef WIFI_INFO_H_ 5 | #define WIFI_INFO_H_ 6 | 7 | #include 8 | 9 | void improv_loop(); 10 | 11 | void wifi_connect(); 12 | 13 | void wifi_scan(); 14 | 15 | bool connect_wifi(const std::string& ssid, const std::string& password, const uint8_t *bssid); 16 | bool connect_wifi(const std::string& ssid, const std::string& password); 17 | 18 | typedef struct 19 | { 20 | String ssid; 21 | int32_t rssi; 22 | int32_t channel; 23 | uint8_t bssid[6]; 24 | } wifiNet_t; 25 | extern std::multiset wifiNets; 26 | extern station_config wifiConf; 27 | 28 | #endif /* WIFI_INFO_H_ */ 29 | -------------------------------------------------------------------------------- /src/www/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/src/www/apple-touch-icon.png -------------------------------------------------------------------------------- /src/www/auth: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/src/www/auth -------------------------------------------------------------------------------- /src/www/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratgdo/homekit-ratgdo/0aa59381ad4babff25429f4df27f7a5d3e6d09aa/src/www/favicon.png -------------------------------------------------------------------------------- /src/www/garage-car.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/www/logs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | HomeKit Garage Door Logs 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | 33 |
34 |

Garage Door

35 |
36 |
37 | 38 |
39 | 40 | 41 | 42 | 43 | 46 |
47 | 48 |
49 |

RATGDO device status...

50 |
51 | Loading...
52 |         
53 |
54 |
55 |

Current server message log...

56 |

57 |     
58 |
59 |

Messages logged immediately before last user requested reboot or reset...

60 |

61 |     
62 |
63 |

Messages logged immediately before last system crash...

64 | 65 |

66 |     
67 | 68 | 78 |
79 | 80 | 81 | 86 | 87 | 88 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/www/logs.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * homekit-ratgdo logger web page javascript functions 3 | * 4 | * Copyright (c) 2024 David Kerr, https://github.com/dkerr64 5 | * 6 | */ 7 | 8 | // Global vars... 9 | var evtSource = undefined; // for Server Sent Events (SSE) 10 | var msgJson = undefined; // for status 11 | const clientUUID = uuidv4(); // uniquely identify this session 12 | 13 | function findStartTime(text) { 14 | const regex = /[\s\r\n]/; 15 | let serverTime = undefined; 16 | let upTime = undefined; 17 | let bootTime = undefined; 18 | 19 | function search(string, regexp, from = 0) { 20 | const index = string.slice(from).search(regexp); 21 | return index === -1 ? -1 : index + from; 22 | } 23 | 24 | let i = text.indexOf(':', text.indexOf('Server uptime')) + 2; 25 | let j = search(text, regex, i); 26 | upTime = Number(text.substring(i, j)); 27 | 28 | i = text.indexOf('Server time'); 29 | if (i >= 0) { 30 | i = text.indexOf(':', i) + 2; 31 | let j = search(text, regex, i); 32 | serverTime = Number(text.substring(i, j)) * 1000; 33 | bootTime = serverTime - upTime; 34 | } 35 | 36 | return { 37 | serverTime: serverTime, 38 | upTime: upTime, 39 | bootTime: bootTime 40 | }; 41 | } 42 | 43 | function insertTimeStamp(text, startTime) { 44 | if (startTime) { 45 | let i = 0; 46 | let date = new Date(); 47 | while ((i = text.indexOf('>>> [', i) + 1) > 0) { 48 | let j = text.indexOf(']', i); 49 | let logTime = Number(text.substring(i + 4, j)); 50 | date.setTime(startTime + logTime); 51 | text = text.substring(0, i - 1) + '[' + date.toJSON() + text.substring(j); 52 | } 53 | } 54 | return text; 55 | } 56 | 57 | function msToTime(duration) { 58 | let seconds = Math.floor((duration / 1000) % 60), 59 | minutes = Math.floor((duration / (1000 * 60)) % 60), 60 | hours = Math.floor((duration / (1000 * 60 * 60)) % 24), 61 | days = Math.floor((duration / (1000 * 60 * 60 * 24))); 62 | 63 | hours = (hours < 10) ? "0" + hours : hours; 64 | minutes = (minutes < 10) ? "0" + minutes : minutes; 65 | seconds = (seconds < 10) ? "0" + seconds : seconds; 66 | 67 | return days + " days " + hours + " hrs " + minutes + " mins " + seconds + " secs"; 68 | } 69 | 70 | function openTab(evt, tabName) { 71 | var i, tabcontent, tablinks; 72 | // Get all elements with class="tabcontent" and hide them 73 | tabcontent = document.getElementsByClassName("tabcontent"); 74 | for (i = 0; i < tabcontent.length; i++) { 75 | tabcontent[i].style.display = "none"; 76 | } 77 | document.getElementById("clearBtn").style.display = "none"; 78 | // Get all elements with class="tablinks" and remove the class "active" 79 | tablinks = document.getElementsByClassName("tablinks"); 80 | for (i = 0; i < tablinks.length; i++) { 81 | tablinks[i].className = tablinks[i].className.replace(" active", ""); 82 | } 83 | // Show the current tab, and add an "active" class to the button that opened the tab 84 | document.getElementById(tabName).style.display = "block"; 85 | evt.currentTarget.className += " active"; 86 | if (tabName === "crashTab") { 87 | if (msgJson?.crashCount > 0) { 88 | document.getElementById("clearBtn").style.display = "inline-block"; 89 | } 90 | } else if (tabName === "statusTab") { 91 | // Refresh status from the server 92 | loaderElem.style.visibility = "visible"; 93 | document.getElementById("statusjson").innerText = ""; 94 | fetch("status.json") 95 | .then((response) => { 96 | if (!response.ok || response.status !== 200) { 97 | reject(`Error requsting status.json, RC: ${response.status}`); 98 | } else { 99 | return response.text(); 100 | } 101 | }) 102 | .then((text) => { 103 | msgJson = JSON.parse(text); 104 | document.getElementById("statusjson").innerText = text; 105 | loaderElem.style.visibility = "hidden"; 106 | }) 107 | .catch(error => console.warn(error)); 108 | } 109 | } 110 | 111 | async function loadLogs() { 112 | // Load all the logs in parallel, showing progress indicator while we do... 113 | let serverBootTime = undefined; 114 | loaderElem.style.visibility = "visible"; 115 | console.log("Start loading server logs and status"); 116 | Promise.allSettled([ 117 | fetch("status.json") 118 | .then((response) => { 119 | if (!response.ok || response.status !== 200) { 120 | reject(`Error requsting status.json, RC: ${response.status}`); 121 | } else { 122 | return response.text(); 123 | } 124 | }) 125 | .then((text) => { 126 | msgJson = JSON.parse(text); 127 | document.getElementById("deviceName").innerHTML = msgJson.deviceName + " Logs"; 128 | document.title = msgJson.deviceName + " Logs"; 129 | document.getElementById("statusjson").innerText = text; 130 | }) 131 | .catch(error => console.warn(error)), 132 | 133 | fetch("showrebootlog") 134 | .then((response) => { 135 | if (!response.ok || response.status !== 200) { 136 | reject(`Error requsting reboot logs, RC: ${response.status}`); 137 | } else { 138 | return response.text(); 139 | } 140 | }) 141 | .then((text) => { 142 | const { serverTime, upTime, bootTime } = findStartTime(text); 143 | const elem = document.getElementById("rebootlog"); 144 | if (bootTime) { 145 | let date = new Date(); 146 | date.setTime(bootTime); 147 | elem.insertAdjacentHTML('beforebegin', `
Server started at:  ${date.toUTCString()}
`); 148 | date.setTime(serverTime); 149 | elem.insertAdjacentHTML('beforebegin', `
Server shutdown at: ${date.toUTCString()}
`); 150 | } 151 | if (upTime) { 152 | elem.insertAdjacentHTML('beforebegin', `
Server upTime:      ${msToTime(upTime)}
`); 153 | } 154 | elem.innerText = insertTimeStamp(text, bootTime); 155 | }) 156 | .catch(error => console.warn(error)), 157 | 158 | fetch("crashlog") 159 | .then((response) => { 160 | if (!response.ok || response.status !== 200) { 161 | reject(`Error requsting crash logs, RC: ${response.status}`); 162 | } else { 163 | return response.text(); 164 | } 165 | }) 166 | .then((text) => { 167 | const { serverTime, upTime, bootTime } = findStartTime(text); 168 | const elem = document.getElementById("crashlog-timestamps"); 169 | if (bootTime) { 170 | let date = new Date(); 171 | date.setTime(bootTime); 172 | elem.insertAdjacentHTML('beforeend', `
Server started at: ${date.toUTCString()}
`); 173 | date.setTime(serverTime); 174 | elem.insertAdjacentHTML('beforeend', `
Server crashed at: ${date.toUTCString()}
`); 175 | } 176 | if (upTime) { 177 | elem.insertAdjacentHTML('beforeend', `
Server upTime:     ${msToTime(upTime)}
`); 178 | } 179 | document.getElementById("crashlog").innerText = insertTimeStamp(text, bootTime); 180 | }) 181 | .catch(error => console.warn(error)), 182 | 183 | fetch("rest/events/subscribe?id=" + clientUUID + "&log=1") 184 | .then((response) => { 185 | if (!response.ok || response.status !== 200) { 186 | reject(`Error registering for Server Sent Events, RC: ${response.status}`); 187 | } else { 188 | return response.text(); 189 | } 190 | }) 191 | .then((text) => { 192 | const evtUrl = text + '?id=' + clientUUID; 193 | console.log(`Register for server sent events at ${evtUrl}`); 194 | evtSource = new EventSource(evtUrl); 195 | evtSource.addEventListener("logger", (event) => { 196 | let divElem = document.getElementById("logTab"); 197 | let scroll = (divElem.scrollHeight - divElem.scrollTop - divElem.clientHeight) < 10; 198 | document.getElementById("showlog").insertAdjacentText('beforeend', insertTimeStamp(event.data, serverBootTime) + "\n"); 199 | // Only scroll the page if we are already at bottom of the page 200 | if (scroll) divElem.scrollTop = divElem.scrollHeight; 201 | }); 202 | evtSource.addEventListener("error", (event) => { 203 | // If an error occurs close the connection. 204 | console.log(`SSE error occurred while attempting to connect to ${evtSource.url}`); 205 | evtSource.close(); 206 | }); 207 | }) 208 | .catch(error => console.warn(error)), 209 | 210 | fetch("showlog") 211 | .then((response) => { 212 | if (!response.ok || response.status !== 200) { 213 | reject(`Error requsting logs, RC: ${response.status}`); 214 | } else { 215 | return response.text(); 216 | } 217 | }) 218 | .then((text) => { 219 | const elem = document.getElementById("showlog"); 220 | const { serverTime, upTime, bootTime } = findStartTime(text); 221 | serverBootTime = bootTime; 222 | if (bootTime) { 223 | let date = new Date(); 224 | date.setTime(bootTime); 225 | elem.insertAdjacentHTML('beforebegin', `
Server started at:   ${date.toUTCString()}
`); 226 | date.setTime(serverTime); 227 | elem.insertAdjacentHTML('beforebegin', `
Server current time: ${date.toUTCString()}
`); 228 | } 229 | if (upTime) { 230 | elem.insertAdjacentHTML('beforebegin', `
Server upTime:       ${msToTime(upTime)}
`); 231 | } 232 | elem.insertAdjacentText('afterbegin', insertTimeStamp(text, serverBootTime)); 233 | let divElem = document.getElementById("logTab"); 234 | // Scroll to the bottom 235 | divElem.scrollTop = divElem.scrollHeight; 236 | }) 237 | .catch(error => console.warn(error)) 238 | ]) 239 | .then((results) => { 240 | // Once all loaded reset the progress indicator 241 | loaderElem.style.visibility = "hidden"; 242 | console.log("All logs loaded"); 243 | //console.log(results); 244 | }); 245 | } 246 | 247 | async function clearCrashLog() { 248 | loaderElem.style.visibility = "visible"; 249 | await fetch('clearcrashlog'); 250 | document.getElementById("clearBtn").style.display = "none"; 251 | if (msgJson) msgJson.crashCount = 0; 252 | document.getElementById("crashlog").innerText = "No crashes saved"; 253 | document.getElementById("crashlog-timestamps").innerText = ""; 254 | loaderElem.style.visibility = "hidden"; 255 | } 256 | // Generate a UUID. Cannot use crypto.randomUUID() because that will only run 257 | // in a secure environment, which is not possible with ratgdo. 258 | function uuidv4() { 259 | return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => 260 | (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16) 261 | ); 262 | } -------------------------------------------------------------------------------- /src/www/qrcode.svg: -------------------------------------------------------------------------------- 1 | 2 | HomeKit QR Code 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/www/settings-sliders.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/www/status.json: -------------------------------------------------------------------------------- 1 | { 2 | "upTime": 847155, 3 | "deviceName": "Garage Door ABCDEF", 4 | "paired": false, 5 | "firmwareVersion": "9.9.9", 6 | "accessoryID": "BA:98:76:54:32:10", 7 | "clients": 1, 8 | "staticIP": true, 9 | "localIP": "192.168.99.99", 10 | "subnetMask": "255.255.255.0", 11 | "gatewayIP": "192.168.99.1", 12 | "nameserverIP": "192.168.99.1", 13 | "macAddress": "02:23:45:AB:CD:EF", 14 | "syslogEn": true, 15 | "syslogIP": "192.168.99.2", 16 | "wifiSSID": "Test", 17 | "wifiRSSI": "-55 dBm, Channel 6", 18 | "wifiBSSID": "AA:BB:CC:DD:EE:FF", 19 | "lockedAP": true, 20 | "GDOSecurityType" : 2, 21 | "garageLightOn": false, 22 | "garageDoorState": "Closed", 23 | "garageLockState": "Unsecured", 24 | "garageMotion": false, 25 | "garageObstructed": false, 26 | "passwordRequired": true, 27 | "rebootSeconds": 0, 28 | "freeHeap": 20000, 29 | "minHeap": 10000, 30 | "minStack": 2000, 31 | "crashCount": 1, 32 | "wifiPhyMode": 0, 33 | "wifiPower": 10, 34 | "TTCseconds": 10, 35 | "motionTriggers": 3, 36 | "LEDidle": 1, 37 | "enableNTP": true, 38 | "lastDoorUpdateAt": 847155, 39 | "timeZone": "America/New_York;EST5EDT,M3.2.0,M11.1.0", 40 | "checkFlashCRC": false 41 | } 42 | -------------------------------------------------------------------------------- /src/www/style.css: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Stylesheet for RATDGO web status page 3 | * 4 | * Copyright (c) 2023-24 David Kerr, https://github.com/dkerr64 5 | * 6 | *************************************************************/ 7 | 8 | * { 9 | box-sizing: border-box; 10 | } 11 | 12 | html, 13 | body { 14 | background-color: darkgray; 15 | overflow-x: hidden; 16 | font-family: Verdana, Geneva, Tahoma, sans-serif; 17 | font-size: 3vw 18 | } 19 | 20 | input, 21 | input[type="file"]::-webkit-file-upload-button, 22 | input[type="file"]::file-selector-button { 23 | /* background-color: rgb(180, 117, 23); */ 24 | background-color: darkgoldenrod; 25 | color: white; 26 | border-style: solid; 27 | border-color: white; 28 | border-top-left-radius: 5px; 29 | border-top-right-radius: 5px; 30 | border-bottom-right-radius: 5px; 31 | border-bottom-left-radius: 5px; 32 | border-width: 1px; 33 | font-weight: bold; 34 | font-size: 2vw; 35 | padding: 5px; 36 | cursor: pointer; 37 | } 38 | 39 | input[type="radio"], 40 | input[type="checkbox"] { 41 | vertical-align: middle; 42 | accent-color: darkgoldenrod; 43 | } 44 | 45 | input.slider { 46 | -webkit-appearance: none; 47 | appearance: none; 48 | vertical-align: middle; 49 | margin: 0; 50 | border: 0; 51 | padding: 0; 52 | height: 15px; 53 | background-color: transparent; 54 | border-radius: 5px; 55 | } 56 | 57 | input.slider::-webkit-slider-runnable-track { 58 | height: 3px; 59 | background-color: #B0B0B0; 60 | } 61 | 62 | input.slider::-webkit-slider-thumb { 63 | -webkit-appearance: none; 64 | appearance: none; 65 | width: 17px; 66 | height: 17px; 67 | border-radius: 50%; 68 | border: 0px; 69 | margin-top: -7px; 70 | background: darkgoldenrod; 71 | cursor: pointer; 72 | } 73 | 74 | input.slider::-moz-range-track { 75 | height: 3px; 76 | background-color: #B0B0B0; 77 | } 78 | 79 | input.slider::-moz-range-thumb { 80 | width: 17px; 81 | height: 17px; 82 | border-radius: 50%; 83 | border: 0px; 84 | margin-top: -7px; 85 | background: darkgoldenrod; 86 | cursor: pointer; 87 | } 88 | 89 | 90 | select { 91 | vertical-align: middle; 92 | font-size: 1em; 93 | border-style: solid; 94 | border-top-left-radius: 5px; 95 | border-top-right-radius: 5px; 96 | border-bottom-right-radius: 5px; 97 | border-bottom-left-radius: 5px; 98 | border-width: 1px; 99 | width: 15em; 100 | padding: 5px; 101 | appearance: unset; 102 | } 103 | 104 | div { 105 | position: static; 106 | float: left; 107 | background-color: #E0E0E0; 108 | border-color: #303030; 109 | padding: 10px; 110 | } 111 | 112 | div.serverstatus { 113 | width: 75%; 114 | overflow: hidden; 115 | position: relative; 116 | 117 | td:nth-child(1) { 118 | text-align: right; 119 | } 120 | } 121 | 122 | div.qrcode { 123 | width: 25%; 124 | max-width: 200px; 125 | overflow: hidden; 126 | position: relative; 127 | } 128 | 129 | div.fullwidth { 130 | width: 100%; 131 | max-width: 800px; 132 | overflow: hidden; 133 | position: relative; 134 | border-bottom-style: solid; 135 | border-bottom-width: 1px; 136 | padding-left: 0px; 137 | padding-right: 0px; 138 | 139 | td:nth-child(2n - 1) { 140 | text-align: right; 141 | } 142 | } 143 | 144 | div.header { 145 | width: 100%; 146 | max-width: 800px; 147 | overflow: hidden; 148 | position: relative; 149 | border-bottom-style: solid; 150 | border-bottom-width: 1px; 151 | padding: 0px; 152 | padding-top: 5px; 153 | padding-bottom: 5px; 154 | } 155 | 156 | div.fullpage { 157 | margin: auto; 158 | max-width: 800px; 159 | height: auto; 160 | overflow: hidden; 161 | position: relative; 162 | float: none; 163 | border-style: solid; 164 | padding-top: 0px; 165 | padding-bottom: 0px; 166 | } 167 | 168 | div.center { 169 | text-align: center; 170 | padding: 0px; 171 | } 172 | 173 | div.footer { 174 | width: 100%; 175 | font-size: 12px; 176 | padding: 0px; 177 | padding-bottom: 5px; 178 | } 179 | 180 | .logo { 181 | font-family: 'Courier New', Courier, monospace; 182 | text-align: left; 183 | white-space: pre; 184 | font-weight: bold; 185 | padding: 0; 186 | margin: 0; 187 | display: inline-grid; 188 | } 189 | 190 | p.logo { 191 | font-family: Verdana, Geneva, Tahoma, sans-serif; 192 | font-weight: normal; 193 | font-size: 2vw; 194 | margin: 0px; 195 | } 196 | 197 | pre.logo { 198 | font-size: 1.5vw; 199 | margin-bottom: -2vw; 200 | } 201 | 202 | div.name { 203 | font-weight: bold; 204 | font-size: 4vw; 205 | text-align: left; 206 | padding: 0; 207 | margin: 0; 208 | margin-left: 2vw; 209 | margin-top: 1.5vw; 210 | height: 100%; 211 | display: flex; 212 | } 213 | 214 | td { 215 | padding: 1px; 216 | vertical-align: top; 217 | } 218 | 219 | table.settings td, 220 | table.settings label, 221 | table.password td, 222 | table.password label { 223 | vertical-align: middle; 224 | font-size: 0.8em; 225 | } 226 | 227 | table.settings td.label, 228 | table.settings td.IPlabel, 229 | table.password td.label { 230 | vertical-align: middle; 231 | text-align: right; 232 | font-size: 0.9em; 233 | } 234 | 235 | table.settings input.TZinput, 236 | table.settings input.IPinput { 237 | font-size: 0.9em; 238 | } 239 | 240 | .modal { 241 | display: none; 242 | position: fixed; 243 | z-index: 1; 244 | padding-top: 100px; 245 | top: 0px; 246 | left: 0px; 247 | width: 100%; 248 | height: 100%; 249 | overflow: auto; 250 | background-color: rgba(0, 0, 0, 0.4); 251 | } 252 | 253 | /* Modal Content */ 254 | .modal-content { 255 | background-color: #E0E0E0; 256 | margin: auto; 257 | padding: 20px; 258 | border: 1px solid gray; 259 | width: 90%; 260 | max-width: 720px; 261 | float: none; 262 | } 263 | 264 | /* The Close Button */ 265 | .close { 266 | color: #aaaaaa; 267 | float: right; 268 | font-size: 28px; 269 | font-weight: bold; 270 | } 271 | 272 | .close:hover, 273 | .close:focus { 274 | color: black; 275 | text-decoration: none; 276 | cursor: pointer; 277 | } 278 | 279 | fieldset { 280 | border: 1px solid darkgray; 281 | width: 100%; 282 | } 283 | 284 | /* Style the tab */ 285 | div.tab { 286 | overflow: hidden; 287 | height: initial; 288 | width: 100%; 289 | position: relative; 290 | border-bottom-style: solid; 291 | border-bottom-width: 1px; 292 | padding-left: 0px; 293 | padding-right: 0px; 294 | padding-top: 5px; 295 | padding-bottom: 1px; 296 | flex-grow: 0; 297 | flex-shrink: 1; 298 | flex-basis: auto; 299 | } 300 | 301 | /* Style the buttons that are used to open the tab content */ 302 | div.tab button { 303 | background-color: darkgoldenrod; 304 | font-weight: bold; 305 | color: white; 306 | float: left; 307 | border-color: transparent; 308 | margin: 1px; 309 | outline: none; 310 | cursor: pointer; 311 | padding: 2vw 1vw; 312 | transition: 0.3s; 313 | font-size: 2vw; 314 | } 315 | 316 | /* Change background color of buttons on hover */ 317 | div.tab button:hover { 318 | background-color: lightgray; 319 | color: black; 320 | } 321 | 322 | /* Create an active/current tablink class */ 323 | div.tab button.active { 324 | background-color: silver; 325 | color: black; 326 | } 327 | 328 | /* Style the tab content */ 329 | div.tabcontent { 330 | display: none; 331 | width: 100%; 332 | height: 0px; 333 | overflow: auto; 334 | position: relative; 335 | border-bottom-style: solid; 336 | border-bottom-width: 1px; 337 | padding: 0px; 338 | margin-bottom: 10px; 339 | margin-top: 10px; 340 | flex-grow: 1; 341 | flex-shrink: 1; 342 | flex-basis: auto; 343 | } 344 | 345 | div.logheader { 346 | max-width: 100%; 347 | height: initial; 348 | flex: 0 1 auto; 349 | border-bottom-style: none; 350 | } 351 | 352 | #loader { 353 | border: 12px solid whitesmoke; 354 | border-radius: 50%; 355 | border-top: 12px solid dimgray; 356 | width: 70px; 357 | height: 70px; 358 | animation: spin 1s linear infinite; 359 | visibility: hidden; 360 | z-index: 99; 361 | } 362 | 363 | .ldrcenter { 364 | position: absolute; 365 | top: 0; 366 | bottom: 0; 367 | left: 0; 368 | right: 0; 369 | margin: auto; 370 | } 371 | 372 | @keyframes spin { 373 | 100% { 374 | transform: rotate(360deg); 375 | } 376 | } 377 | 378 | @media only screen and (min-width: 660px) { 379 | body { 380 | font-size: 19.8px; 381 | } 382 | 383 | div.tab button { 384 | padding: 14px 6px; 385 | font-size: 13.2px; 386 | } 387 | 388 | input, 389 | input[type="file"]::-webkit-file-upload-button, 390 | input[type="file"]::file-selector-button { 391 | font-size: 13.2px; 392 | } 393 | 394 | div.name { 395 | font-size: 26.4px; 396 | margin-left: 13.2px; 397 | margin-top: 9.9px; 398 | } 399 | 400 | pre.logo { 401 | font-size: 9.9px; 402 | margin-bottom: -13.2px; 403 | } 404 | 405 | p.logo { 406 | font-size: 13.2px; 407 | } 408 | } 409 | 410 | @media (prefers-color-scheme: light) { 411 | 412 | .modal-content, 413 | html, 414 | div, 415 | input[type="file"], 416 | body { 417 | background-color: #E0E0E0; 418 | border-color: #303030; 419 | color: black; 420 | } 421 | 422 | select, 423 | input[type="number"], 424 | input[type="text"], 425 | input[type="password"] { 426 | background-color: #F0F0F0; 427 | border-color: #303030; 428 | color: black; 429 | } 430 | 431 | div.footer, 432 | a { 433 | color: #505050; 434 | } 435 | } 436 | 437 | @media (prefers-color-scheme: dark) { 438 | 439 | .modal-content, 440 | html, 441 | div, 442 | input[type="file"], 443 | body { 444 | background-color: #303030; 445 | border-color: #E0E0E0; 446 | color: white; 447 | } 448 | 449 | select, 450 | input[type="number"], 451 | input[type="text"], 452 | input[type="password"] { 453 | background-color: #202020; 454 | border-color: #E0E0E0; 455 | color: white; 456 | } 457 | 458 | div.footer, 459 | a { 460 | color: #B0B0B0; 461 | } 462 | } -------------------------------------------------------------------------------- /test/test_packet/secplus.c: -------------------------------------------------------------------------------- 1 | ../../lib/secplus/src/secplus.c -------------------------------------------------------------------------------- /test/test_packet/test_main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | void setUp(void) { 7 | } 8 | 9 | void tearDown(void) { 10 | } 11 | 12 | /* 13 | * uint32_t rolling = 0; 14 | * uint64_t fixed = 0; 15 | * uint32_t data = 0; 16 | * decode_wireline(test_data, &rolling, &fixed, &data); 17 | * uint16_t cmd = ((fixed >> 24) & 0xf00) | (data & 0xff); 18 | * printf("DOOR_ACTION\n"); 19 | * printf("rolling %X, fixed %llX, data %X\n", rolling, fixed, data); 20 | * printf("cmd %X\n", cmd); 21 | */ 22 | 23 | void test_packet_status_recd(void) { 24 | uint8_t test_data[SECPLUS2_CODE_LEN] = { 25 | 0x55, 0x01, 0x00, 0xA5, 0x2F, 0xB3, 0xDB, 0xCE, 0x8F, 0x5B, 0x0C, 0x40, 0x34, 0xB9, 0x71, 0x96, 0x73, 0xFD, 0xBA }; 26 | 27 | Packet pkt = Packet(test_data); 28 | TEST_ASSERT_EQUAL(PacketCommand::Status, pkt.m_pkt_cmd); 29 | TEST_ASSERT_EQUAL(PacketDataType::Status, pkt.m_data.type); 30 | TEST_ASSERT_EQUAL(DoorState::Closed, pkt.m_data.value.status.door); 31 | TEST_ASSERT_TRUE(pkt.m_data.value.status.obstruction); 32 | TEST_ASSERT_TRUE(pkt.m_data.value.status.light); 33 | TEST_ASSERT_FALSE(pkt.m_data.value.status.lock); 34 | 35 | TEST_ASSERT_EQUAL_HEX(0x52402A, pkt.m_remote_id); 36 | TEST_ASSERT_EQUAL_HEX(0x17702, pkt.m_rolling); 37 | } 38 | 39 | void test_packet_door_action_xmit(void) { 40 | PacketData data; 41 | data.type = PacketDataType::DoorAction; 42 | data.value.door_action.action = DoorAction::Toggle; 43 | data.value.door_action.parity = 0b1000; // to match command below 44 | data.value.door_action.pressed = true; 45 | data.value.door_action.id = 1; 46 | 47 | Packet pkt = Packet(PacketCommand::DoorAction, data, 0x539); 48 | 49 | uint8_t test_data[SECPLUS2_CODE_LEN] = { 50 | 0x55, 0x01, 0x00, 0xA0, 0x37, 0xDF, 0x77, 0xB6, 0xFB, 0xED, 0xB0, 0x88, 0x22, 0x91, 0x05, 0x21, 0x72, 0x4D, 0x2C }; 51 | 52 | uint8_t encode_output[SECPLUS2_CODE_LEN]; 53 | TEST_ASSERT_EQUAL(0, pkt.encode(0x48, encode_output)); 54 | TEST_ASSERT_EQUAL_MEMORY(test_data, encode_output, SECPLUS2_CODE_LEN); 55 | } 56 | 57 | /* 58 | * This test includes an OG "sync" packet from the ratgdo code ("reboot1"), which has a mystery data 59 | * bit set (bit 0 of byte 1, or 0x100). There isn't a way to represent that data bit when building a 60 | * GetOpenings packet, so this test is disabled (but would otherwise pass). 61 | void test_packet_get_openings(void) { 62 | uint8_t test_data[SECPLUS2_CODE_LEN] = { 63 | 0x55, 0x01, 0x00, 0x94, 0x3F, 0xFD, 0xE7, 0xDF, 0x7F, 0xBE, 0xFF, 0x52, 0x0C, 0x26, 0xDA, 0x4E, 0xA9, 0x8A, 0x67 }; 64 | 65 | Packet pkt = Packet(test_data); 66 | TEST_ASSERT_EQUAL_HEX(PacketCommand::GetOpenings, pkt.m_pkt_cmd); 67 | TEST_ASSERT_EQUAL(PacketDataType::NoData, pkt.m_data.type); 68 | 69 | TEST_ASSERT_EQUAL_HEX(0x539, pkt.m_remote_id); 70 | TEST_ASSERT_EQUAL_HEX(0xDB, pkt.m_rolling); 71 | 72 | uint8_t roundtrip[SECPLUS2_CODE_LEN]; 73 | TEST_ASSERT_EQUAL(0, pkt.encode(roundtrip)); 74 | TEST_ASSERT_EQUAL_MEMORY(test_data, roundtrip, SECPLUS2_CODE_LEN); 75 | } 76 | */ 77 | 78 | void print_packet(uint8_t pkt[SECPLUS2_CODE_LEN]) { 79 | printf("decoded packet: [%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X]\n", 80 | pkt[0], pkt[1], pkt[2], pkt[3], pkt[4], pkt[5], pkt[6], pkt[7], pkt[8], pkt[9], 81 | pkt[10], pkt[11], pkt[12], pkt[13], pkt[14], pkt[15], pkt[16], pkt[17], pkt[18]); 82 | } 83 | 84 | /* 85 | * This is just a landing zone for printing a bunch of random packets sourced from where ever. They 86 | * may or may not illustrate anything at all. 87 | */ 88 | void print_some_packets(void) { 89 | /* 90 | uint8_t one[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x99, 0x02, 0x11, 0x40, 0x8E, 0x8D, 0x48, 0x0C, 0x65, 0x29, 0x85, 0xC7, 0x7D, 0xC0, 0xCA, 0x2B}; 91 | print_packet(one); 92 | Packet p = Packet(one); 93 | p.print(); 94 | 95 | uint8_t one_out[SECPLUS2_CODE_LEN] = {1}; 96 | TEST_ASSERT_EQUAL(0, p.encode(one_out)); 97 | print_packet(one_out); 98 | Packet q = Packet(one_out); 99 | q.print(); 100 | TEST_ASSERT_EQUAL_MEMORY(one, one_out, SECPLUS2_CODE_LEN); 101 | 102 | uint8_t two[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x54, 0x17, 0x21, 0xEE, 0xAB, 0xE1, 0xEF, 0xAF, 0x06, 0x19, 0x2F, 0xC6, 0x53, 0xCD, 0xB6, 0x4E }; 103 | Packet(two).print(); 104 | 105 | */ 106 | uint8_t pkt1[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x00, 0x36, 0xDB, 0x2D, 0xB6, 0xDB, 0x6D, 0xB6, 0x00, 0x36, 0xFD, 0xBC, 0xB6, 0xE9, 0x6F, 0xBB}; 107 | Packet(pkt1).print(); 108 | uint8_t pkt2[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0xAA, 0x2C, 0xB2, 0x59, 0x6D, 0x96, 0x59, 0x61, 0xA0, 0x36, 0x94, 0x0C, 0xB7, 0x3B, 0xAD, 0xB6}; 109 | Packet(pkt2).print(); 110 | uint8_t pkt3[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x55, 0x2D, 0x96, 0xCB, 0x2C, 0xB2, 0xCB, 0x2C, 0x50, 0x37, 0x68, 0x0D, 0x36, 0x1C, 0x5D, 0xB4}; 111 | Packet(pkt3).print(); 112 | uint8_t pkt4[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x54, 0x37, 0xFB, 0xEF, 0xBE, 0xFB, 0xEF, 0xBD, 0x41, 0x02, 0xDB, 0xF4, 0xD0, 0xE1, 0x34, 0x90}; 113 | Packet(pkt4).print(); 114 | uint8_t pkt5[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x22, 0x09, 0x20, 0x99, 0x49, 0x24, 0x12, 0x41, 0x20, 0x3E, 0x94, 0x0C, 0xB6, 0x1B, 0xCD, 0xA6}; 115 | Packet(pkt5).print(); 116 | uint8_t pkt6[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x21, 0x1A, 0x49, 0x2F, 0x92, 0x49, 0xA4, 0x93, 0x11, 0x32, 0xEC, 0x94, 0x16, 0x29, 0x74, 0x9E}; 117 | Packet(pkt6).print(); 118 | uint8_t pkt7[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x99, 0x30, 0x3D, 0x6A, 0x07, 0x80, 0x48, 0x04, 0x42, 0x0D, 0x64, 0x73, 0x43, 0x88, 0x03, 0x5D}; 119 | Packet(pkt7).print(); 120 | uint8_t pkt8[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x44, 0x1D, 0x89, 0xBD, 0xEA, 0xF7, 0xFF, 0x7A, 0xA2, 0x03, 0x26, 0xA2, 0xE9, 0xC4, 0x12, 0x61}; 121 | Packet(pkt8).print(); 122 | uint8_t pkt9[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x44, 0x1D, 0x89, 0xBD, 0xEA, 0xF7, 0xFF, 0x7A, 0xA2, 0x03, 0x26, 0xA2, 0xE9, 0xC4, 0x12, 0x61}; 123 | Packet(pkt9).print(); 124 | uint8_t pkt10[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x44, 0x1D, 0x89, 0xBD, 0xEA, 0xF7, 0xFF, 0x7E, 0xA2, 0x03, 0x26, 0xA2, 0xE9, 0xC4, 0x52, 0x61}; 125 | Packet(pkt10).print(); 126 | uint8_t pkt11[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x09, 0x08, 0xF4, 0x80, 0x71, 0x14, 0x84, 0x22, 0x59, 0x08, 0x01, 0x60, 0x61, 0xCC, 0x32, 0x85}; 127 | Packet(pkt11).print(); 128 | uint8_t pkt12[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x09, 0x08, 0xF4, 0x88, 0x71, 0x00, 0x04, 0x02, 0x59, 0x08, 0x01, 0x60, 0x61, 0xCD, 0x13, 0x01}; 129 | Packet(pkt12).print(); 130 | uint8_t pkt13[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x42, 0x29, 0x1A, 0xD0, 0x5C, 0x2C, 0x92, 0x59, 0x94, 0x1D, 0xEF, 0x1E, 0x73, 0x1B, 0x2E, 0x7D}; 131 | Packet(pkt13).print(); 132 | uint8_t pkt14[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x09, 0x08, 0xF4, 0x88, 0x71, 0x00, 0x04, 0x02, 0x59, 0x08, 0x01, 0x60, 0x61, 0xCD, 0x13, 0x01}; 133 | Packet(pkt14).print(); 134 | uint8_t pkt15[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x54, 0x17, 0x21, 0xEE, 0xAB, 0xE1, 0xEF, 0xAF, 0x06, 0x19, 0x2F, 0xC6, 0x53, 0xCD, 0xB6, 0x4E}; 135 | Packet(pkt15).print(); 136 | uint8_t pkt16[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x42, 0x29, 0x1A, 0xD0, 0x5C, 0x2C, 0x92, 0x59, 0x94, 0x1D, 0xEF, 0x1E, 0x73, 0x1B, 0x2E, 0x7D}; 137 | Packet(pkt16).print(); 138 | uint8_t pkt17[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x42, 0x29, 0x1A, 0xD0, 0x5C, 0x2C, 0x92, 0x59, 0x94, 0x1D, 0xEF, 0x1E, 0x73, 0x1B, 0x2E, 0x7D}; 139 | Packet(pkt17).print(); 140 | uint8_t pkt18[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x42, 0x29, 0x1A, 0xD0, 0x5C, 0x2C, 0x92, 0x59, 0x94, 0x1D, 0xEF, 0x1E, 0x73, 0x1B, 0x2E, 0x7D}; 141 | Packet(pkt18).print(); 142 | uint8_t pkt19[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x42, 0x29, 0x1A, 0xD0, 0x5C, 0x2C, 0x92, 0x59, 0x94, 0x1D, 0xEF, 0x1E, 0x73, 0x1B, 0x2E, 0x7D}; 143 | Packet(pkt19).print(); 144 | uint8_t pkt20[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x89, 0x30, 0x36, 0x62, 0x85, 0x40, 0x04, 0x07, 0x41, 0x06, 0x48, 0xE5, 0x1A, 0xE1, 0x24, 0x98}; 145 | Packet(pkt20).print(); 146 | uint8_t pkt21[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x89, 0x30, 0x36, 0x62, 0x85, 0x40, 0x04, 0x07, 0x41, 0x06, 0x48, 0xE5, 0x1A, 0xE1, 0x24, 0x98}; 147 | Packet(pkt21).print(); 148 | uint8_t pkt22[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x89, 0x30, 0x36, 0x62, 0x85, 0x40, 0x04, 0x03, 0x41, 0x06, 0x48, 0xE5, 0x1A, 0xE1, 0x34, 0x98}; 149 | Packet(pkt22).print(); 150 | uint8_t pkt23[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x88, 0x04, 0xE4, 0x2B, 0xA1, 0xD2, 0x4D, 0x24, 0x22, 0x03, 0x22, 0x30, 0xE8, 0xF6, 0x52, 0xC3}; 151 | Packet(pkt23).print(); 152 | uint8_t pkt24[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x52, 0x28, 0xFE, 0x83, 0x5D, 0x3A, 0x82, 0x51, 0xA8, 0x2C, 0x90, 0x3B, 0x35, 0x62, 0xCA, 0x22}; 153 | Packet(pkt24).print(); 154 | uint8_t pkt25[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x88, 0x04, 0xE4, 0x2B, 0xA1, 0xD2, 0x4D, 0x24, 0x22, 0x03, 0x22, 0x30, 0xE8, 0xF6, 0x52, 0xC3}; 155 | Packet(pkt25).print(); 156 | uint8_t pkt26[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x52, 0x28, 0xFE, 0x87, 0x5D, 0x20, 0x82, 0x41, 0xA8, 0x2C, 0x90, 0x3B, 0x35, 0x60, 0xCB, 0xA4}; 157 | Packet(pkt26).print(); 158 | uint8_t pkt27[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x52, 0x28, 0xFE, 0x87, 0x5D, 0x20, 0x82, 0x41, 0xA8, 0x2C, 0x90, 0x3B, 0x35, 0x60, 0xCB, 0xA4}; 159 | Packet(pkt27).print(); 160 | uint8_t pkt28[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x52, 0x28, 0xFE, 0x87, 0x5D, 0x20, 0x82, 0x41, 0xA8, 0x2C, 0x90, 0x3B, 0x35, 0x60, 0xCB, 0xA4}; 161 | Packet(pkt28).print(); 162 | uint8_t pkt29[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0xA9, 0x10, 0xD9, 0x82, 0xAA, 0x39, 0x82, 0x21, 0x56, 0x1B, 0x68, 0xC4, 0xFB, 0xA8, 0x86, 0x87}; 163 | Packet(pkt29).print(); 164 | uint8_t pkt30[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x88, 0x26, 0x93, 0x4B, 0x34, 0xD2, 0x4D, 0x21, 0x80, 0x34, 0x49, 0xBD, 0xF6, 0x3B, 0x6D, 0xBE}; 165 | Packet(pkt30).print(); 166 | uint8_t pkt31[SECPLUS2_CODE_LEN] = {0x55, 0x01, 0x00, 0x88, 0x26, 0x93, 0x4B, 0x34, 0xD2, 0x4D, 0x25, 0x80, 0x34, 0x49, 0xBD, 0xF6, 0x3B, 0x7D, 0xBE}; 167 | Packet(pkt31).print(); 168 | } 169 | 170 | int main(int argc, char **argv) { 171 | UNITY_BEGIN(); 172 | RUN_TEST(test_packet_status_recd); 173 | RUN_TEST(test_packet_door_action_xmit); 174 | // RUN_TEST(test_packet_get_openings); 175 | RUN_TEST(print_some_packets); 176 | UNITY_END(); 177 | 178 | return 0; 179 | } 180 | -------------------------------------------------------------------------------- /test/test_reader/secplus.c: -------------------------------------------------------------------------------- 1 | ../../lib/secplus/src/secplus.c -------------------------------------------------------------------------------- /test/test_reader/test_main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | SecPlus2Reader r; 7 | 8 | // print_packet stub 9 | void print_packet(uint8_t* pkt) {} 10 | 11 | void setUp(void) { 12 | } 13 | 14 | void tearDown(void) { 15 | } 16 | 17 | void test_reader_result(void) { 18 | uint8_t test_data[SECPLUS2_CODE_LEN] = { 19 | 0x55, 0x01, 0x00, 20 | 0x99, 0x02, 0x11, 0x40, 0x8E, 0x8D, 0x48, 0x0C, 0x65, 0x29, 0x85, 0xC7, 0x7D, 0xC0, 0xCA, 0x2B }; 21 | 22 | bool res = false; 23 | uint8_t i = 0; 24 | while (i < SECPLUS2_CODE_LEN) { 25 | res = r.push_byte(test_data[i]); 26 | i += 1; 27 | if (res) { 28 | break; 29 | } 30 | } 31 | 32 | TEST_ASSERT_EQUAL(SECPLUS2_CODE_LEN, i); 33 | TEST_ASSERT_EQUAL_MEMORY(test_data, r.fetch_buf(), SECPLUS2_CODE_LEN); 34 | } 35 | 36 | int main(int argc, char **argv) { 37 | UNITY_BEGIN(); 38 | RUN_TEST(test_reader_result); 39 | UNITY_END(); 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /upload_firmware.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | IP=${1} 3 | FILE=${2} 4 | if [ $(which md5sum) ]; then 5 | # Linux 6 | MD5=$(md5sum "${FILE}" | cut -d' ' -f1) 7 | SIZE=$(stat -c%s "${FILE}") 8 | elif [ $(which md5) ]; then 9 | # macOS 10 | MD5=$(md5 -q "${FILE}") 11 | SIZE=$(stat -f%z "${FILE}") 12 | fi 13 | if [ -z "${MD5}" ]; then 14 | echo "Unable to calculate MD5 hash for file ${$FILE}, terminating" 15 | exit 1 16 | fi 17 | echo "Calculated MD5 hash for file ${FILE}: ${MD5}, Size: ${SIZE} bytes" 18 | JSON="{\"md5\":\"${MD5}\",\"size\":${SIZE},\"uuid\":\"n/a\"}" 19 | #echo "Inform host of file size and MD5 hash..." 20 | #curl -s -X POST -F "updateUnderway=${JSON}" "http://${IP}/setgdo" > /dev/null 21 | echo "Checking RATGGO flash CRC..." 22 | RESPONSE=$(curl -s "http://${IP}/checkflash") 23 | if [ "${RESPONSE}" != "true" ]; then 24 | echo "checkFlashCRC failed on server ${IP}. You must flash new firmware by USB cable to recover. See documentation." 25 | exit 1 26 | fi 27 | echo "Uploading file..." 28 | RESPONSE=$(curl -s -w ">>>>>%{http_code}" -F "content=@${FILE}" "http://${IP}/update?action=update&size=${SIZE}&md5=${MD5}") 29 | BODY=$(echo ${RESPONSE} | awk -F'>>>>>' '{print $1}') 30 | RC=$(echo ${RESPONSE} | awk -F'>>>>>' '{print $2}') 31 | echo ${BODY} 32 | if [ "${RC}" = "200" ]; then 33 | echo "Success, reboot RATGDO, please allow 30 seconds to complete..." 34 | curl -s -X POST "http://${IP}/reboot" 35 | exit 0 36 | fi 37 | echo "Upload failure, RATGDO device NOT rebooted..." 38 | echo "To reboot device issue command: curl -s -X POST http://${IP}/reboot" 39 | exit 1 40 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | def chunker(seq, size): 2 | return (seq[pos:pos + size] for pos in range(0, len(seq), size)) 3 | 4 | def split_pkt(pkt): 5 | """ 6 | Split a hex-encoded string into 0x-prefixed bytes suitable for including in a C file 7 | 8 | ``` 9 | >>> do("5501004A2BB4FAE1A8DF759112783886AD64D5") 10 | 0x55, 0x01, 0x00, 0x4A, 0x2B, 0xB4, 0xFA, 0xE1, 0xA8, 0xDF, 0x75, 0x91, 0x12, 0x78, 0x38, 0x86, 0xAD, 0x64, 0xD5 11 | ``` 12 | """ 13 | print(f'0x{", 0x".join(chunker(pkt, 2))}') 14 | -------------------------------------------------------------------------------- /verify_firmware.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | IP=${1} 3 | FILE=${2} 4 | if [ $(which md5sum) ]; then 5 | # Linux 6 | MD5=$(md5sum "${FILE}" | cut -d' ' -f1) 7 | SIZE=$(stat -c%s "${FILE}") 8 | elif [ $(which md5) ]; then 9 | # macOS 10 | MD5=$(md5 -q "${FILE}") 11 | SIZE=$(stat -f%z "${FILE}") 12 | fi 13 | if [ -z "${MD5}" ]; then 14 | echo "Unable to calculate MD5 hash for file ${$FILE}, terminating" 15 | exit 1 16 | fi 17 | echo "Calculated MD5 hash for file ${FILE}: ${MD5}, Size: ${SIZE} bytes" 18 | JSON="{\"md5\":\"${MD5}\",\"size\":${SIZE},\"uuid\":\"n/a\"}" 19 | #echo "Inform host of file size and MD5 hash..." 20 | #curl -s -X POST -F "updateUnderway=${JSON}" "http://${IP}/setgdo" > /dev/null 21 | echo "Verifying file..." 22 | #curl -s -F "content=@${FILE}" "http://${IP}/verify" 23 | curl -s -F "content=@${FILE}" "http://${IP}/update?action=verify&size=${SIZE}&md5=${MD5}" 24 | echo "Verify complete" 25 | exit 0 26 | -------------------------------------------------------------------------------- /viewlog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | UUID=$(uuidgen) 3 | URL=$(curl -s "http://${1}/rest/events/subscribe?id=${UUID}&log") 4 | curl -s "http://${1}/showlog" 5 | curl -s -N "http://${1}${URL}?id=${UUID}" | sed -u -n '/event: logger/{n;p;}' | cut -c 7- 6 | exit 0 7 | -------------------------------------------------------------------------------- /x.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh - 2 | 3 | while test $# -gt 0 4 | do 5 | case "$1" in 6 | -v) VERBOSE="-vvv" 7 | ;; 8 | upload) pio run -t upload -e ratgdo_esp8266_hV25 9 | ;; 10 | monitor) pio device monitor -e ratgdo_esp8266_hV25 11 | ;; 12 | run) pio run -e ratgdo_esp8266_hV25 $VERBOSE 13 | ;; 14 | test) pio test -e native $VERBOSE 15 | ;; 16 | release) 17 | git tag $2 18 | ./x.sh run 19 | cp .pio/build/ratgdo_esp8266_hV25/firmware.bin docs/firmware/homekit-ratgdo-$(git describe --tag).bin 20 | vi docs/manifest.json 21 | git add docs 22 | git commit -m "Release $2" 23 | git push 24 | git push --tag 25 | ;; 26 | *) echo "usage: x.sh [-v] " 27 | exit 1 28 | ;; 29 | esac 30 | shift 31 | done 32 | 33 | exit 0 34 | --------------------------------------------------------------------------------