├── .ackrc ├── .clang-format ├── .clang-format-ignore ├── .flake8 ├── .github └── workflows │ ├── build.yml │ └── lint.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── assets └── esp8266.png ├── components └── ping │ ├── __init__.py │ ├── ping.h │ ├── ping_esp32.h │ ├── ping_esp8266.h │ ├── ping_sock.c │ ├── ping_sock.h │ └── sensor.py ├── config ├── .gitignore ├── influxdb.yaml ├── ping-esp32.yaml ├── ping-esp32c3.yaml ├── ping.yaml └── secrets.yaml.dist └── pylintrc /.ackrc: -------------------------------------------------------------------------------- 1 | --ignore-dir=config/.esphome 2 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | AccessModifierOffset: -1 3 | AlignAfterOpenBracket: Align 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveDeclarations: false 6 | AlignEscapedNewlines: DontAlign 7 | AlignOperands: true 8 | AlignTrailingComments: true 9 | AllowAllParametersOfDeclarationOnNextLine: true 10 | AllowShortBlocksOnASingleLine: false 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: false 14 | AllowShortLoopsOnASingleLine: false 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: false 17 | AlwaysBreakTemplateDeclarations: MultiLine 18 | BinPackArguments: true 19 | BinPackParameters: true 20 | BraceWrapping: 21 | AfterClass: false 22 | AfterControlStatement: false 23 | AfterEnum: false 24 | AfterFunction: false 25 | AfterNamespace: false 26 | AfterObjCDeclaration: false 27 | AfterStruct: false 28 | AfterUnion: false 29 | AfterExternBlock: false 30 | BeforeCatch: false 31 | BeforeElse: false 32 | IndentBraces: false 33 | SplitEmptyFunction: true 34 | SplitEmptyRecord: true 35 | SplitEmptyNamespace: true 36 | BreakBeforeBinaryOperators: None 37 | BreakBeforeBraces: Attach 38 | BreakBeforeInheritanceComma: false 39 | BreakInheritanceList: BeforeColon 40 | BreakBeforeTernaryOperators: true 41 | BreakConstructorInitializersBeforeComma: false 42 | BreakConstructorInitializers: BeforeColon 43 | BreakAfterJavaFieldAnnotations: false 44 | BreakStringLiterals: true 45 | ColumnLimit: 120 46 | CommentPragmas: '^ IWYU pragma:' 47 | CompactNamespaces: false 48 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 49 | ConstructorInitializerIndentWidth: 4 50 | ContinuationIndentWidth: 4 51 | Cpp11BracedListStyle: true 52 | DerivePointerAlignment: false 53 | DisableFormat: false 54 | ExperimentalAutoDetectBinPacking: false 55 | FixNamespaceComments: true 56 | ForEachMacros: 57 | - foreach 58 | - Q_FOREACH 59 | - BOOST_FOREACH 60 | IncludeBlocks: Preserve 61 | IncludeCategories: 62 | - Regex: '^' 63 | Priority: 2 64 | - Regex: '^<.*\.h>' 65 | Priority: 1 66 | - Regex: '^<.*' 67 | Priority: 2 68 | - Regex: '.*' 69 | Priority: 3 70 | IncludeIsMainRegex: '([-_](test|unittest))?$' 71 | IndentCaseLabels: true 72 | IndentPPDirectives: None 73 | IndentWidth: 2 74 | IndentWrappedFunctionNames: false 75 | KeepEmptyLinesAtTheStartOfBlocks: false 76 | MacroBlockBegin: '' 77 | MacroBlockEnd: '' 78 | MaxEmptyLinesToKeep: 1 79 | NamespaceIndentation: None 80 | PenaltyBreakAssignment: 2 81 | PenaltyBreakBeforeFirstCallParameter: 1 82 | PenaltyBreakComment: 300 83 | PenaltyBreakFirstLessLess: 120 84 | PenaltyBreakString: 1000 85 | PenaltyBreakTemplateDeclaration: 10 86 | PenaltyExcessCharacter: 1000000 87 | PenaltyReturnTypeOnItsOwnLine: 2000 88 | PointerAlignment: Right 89 | RawStringFormats: 90 | - Language: Cpp 91 | Delimiters: 92 | - cc 93 | - CC 94 | - cpp 95 | - Cpp 96 | - CPP 97 | - 'c++' 98 | - 'C++' 99 | CanonicalDelimiter: '' 100 | BasedOnStyle: google 101 | - Language: TextProto 102 | Delimiters: 103 | - pb 104 | - PB 105 | - proto 106 | - PROTO 107 | EnclosingFunctions: 108 | - EqualsProto 109 | - EquivToProto 110 | - PARSE_PARTIAL_TEXT_PROTO 111 | - PARSE_TEST_PROTO 112 | - PARSE_TEXT_PROTO 113 | - ParseTextOrDie 114 | - ParseTextProtoOrDie 115 | CanonicalDelimiter: '' 116 | BasedOnStyle: google 117 | ReflowComments: true 118 | SortIncludes: false 119 | SortUsingDeclarations: false 120 | SpaceAfterCStyleCast: true 121 | SpaceAfterTemplateKeyword: false 122 | SpaceBeforeAssignmentOperators: true 123 | SpaceBeforeCpp11BracedList: false 124 | SpaceBeforeCtorInitializerColon: true 125 | SpaceBeforeInheritanceColon: true 126 | SpaceBeforeParens: ControlStatements 127 | SpaceBeforeRangeBasedForLoopColon: true 128 | SpaceInEmptyParentheses: false 129 | SpacesBeforeTrailingComments: 2 130 | SpacesInAngles: false 131 | SpacesInContainerLiterals: false 132 | SpacesInCStyleCastParentheses: false 133 | SpacesInParentheses: false 134 | SpacesInSquareBrackets: false 135 | Standard: Auto 136 | TabWidth: 2 137 | UseTab: Never 138 | -------------------------------------------------------------------------------- /.clang-format-ignore: -------------------------------------------------------------------------------- 1 | # imported from esp-idf 2 | components/ping/ping_sock.c 3 | components/ping/ping_sock.h 4 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: build 3 | on: 4 | pull_request: 5 | push: 6 | schedule: 7 | - cron: "0 6 * * SUN" 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | config: 14 | - config/ping-esp32.yaml 15 | - config/ping.yaml 16 | - config/influxdb.yaml 17 | - config/ping-esp32c3.yaml 18 | esphome_version: 19 | - default 20 | - latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | 25 | - name: Install python 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: '3.10' 29 | 30 | - name: Install pipenv 31 | run: | 32 | pip install --user pipenv 33 | 34 | - name: Run pipenv 35 | run: | 36 | pipenv install 37 | 38 | - name: Upgrade esphome to the latest if esphome_version is latest 39 | if: matrix.esphome_version == 'latest' 40 | run: | 41 | pipenv update "esphome" 42 | 43 | - name: Compile ping.yaml 44 | run: | 45 | cp config/secrets.yaml.dist config/secrets.yaml 46 | pipenv run esphome version 47 | pipenv run esphome compile ${{ matrix.config }} 48 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: lint 3 | on: 4 | - pull_request 5 | - push 6 | jobs: 7 | lint: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | 13 | - name: Install python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: '3.10' 17 | 18 | - name: Install pipenv 19 | run: | 20 | pip install --user pipenv 21 | 22 | - name: Run pipenv 23 | run: | 24 | pipenv install --dev 25 | 26 | - name: Run black 27 | run: | 28 | pipenv run black --verbose --check components 29 | 30 | - name: Run pylint 31 | run: | 32 | pipenv run pylint -f parseable --persistent=n --rcfile=pylintrc components 33 | 34 | - name: Run flake8 35 | run: | 36 | pipenv run flake8 components 37 | 38 | - name: Run clang-format style check 39 | uses: jidicula/clang-format-action@v4.5.0 40 | with: 41 | clang-format-version: '11' 42 | check-path: 'components' 43 | exclude-regex: 'ping/ping_sock.[ch]' 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #/config/ping 2 | /config/.esphome 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Release 1.1.0 2 | 3 | * 9a6a187 (origin/README) bugfix: s/path/url/ 4 | * 29cf962 (origin/issue_25) bugfix: pass keyword arguments instead of arguments 5 | * 2db6c39 update: esphome to 2022.3.1 6 | * 37faadd ci: run CI with the latest esphome in a matrix 7 | * fa45d7c bugfix: run build workflow every sunday 8 | * ec2ec30 (origin/issue_23) bugfix: pass keyword arguments instead of arguments 9 | * 2c4300e update: esphome to 2022.3.1 10 | * e878ebc ci: add .ackrc 11 | * 78fdd6f feat: support esp32c3 12 | * c922f2d bugfix: document update_interval 13 | * 30fe32a bugfix: start ping session in update() instead of setup() 14 | * 56aad3f bugfix: update ping_sock.c to one in latest esp-idf release 15 | * 221e20f bugfix: replace underscore for device name 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Tomoyuki Sakurai 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | esphome = "2022.9.4" 8 | 9 | [dev-packages] 10 | pylint = "2.8.2" 11 | flake8 = "3.9.2" 12 | black = "21.7b0" 13 | 14 | [requires] 15 | python_version = "3.10" 16 | 17 | [pipenv] 18 | allow_prereleases = true 19 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "b56f00eaaf19403cc6b397c36d58640a76cea0629e17e56990b43118f7cb2eff" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "aioesphomeapi": { 20 | "hashes": [ 21 | "sha256:0b4e31b2aac0808694c09a2b06a3ea417fdfc6ff76435375bd922b077d6a2f74", 22 | "sha256:52e8142c5cbd574064abdb9a49a43c9d58178f8ce933ae334240193a326162d5" 23 | ], 24 | "markers": "python_version >= '3.7'", 25 | "version": "==10.8.2" 26 | }, 27 | "aiofiles": { 28 | "hashes": [ 29 | "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937", 30 | "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59" 31 | ], 32 | "markers": "python_version >= '3.6' and python_version < '4.0'", 33 | "version": "==0.8.0" 34 | }, 35 | "ajsonrpc": { 36 | "hashes": [ 37 | "sha256:0fa2c1cf8e619d18ffee96043822032d6520eda65d3b712f9540a3a63e9cac25", 38 | "sha256:791bac18f0bf0dee109194644f151cf8b7ff529c4b8d6239ac48104a3251a19f" 39 | ], 40 | "markers": "python_version >= '3.5'", 41 | "version": "==1.2.0" 42 | }, 43 | "anyio": { 44 | "hashes": [ 45 | "sha256:a0aeffe2fb1fdf374a8e4b471444f0f3ac4fb9f5a5b542b48824475e0042a5a6", 46 | "sha256:b5fa16c5ff93fa1046f2eeb5bbff2dad4d3514d6cda61d02816dba34fa8c3c2e" 47 | ], 48 | "markers": "python_full_version >= '3.6.2'", 49 | "version": "==3.5.0" 50 | }, 51 | "asgiref": { 52 | "hashes": [ 53 | "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0", 54 | "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9" 55 | ], 56 | "markers": "python_version >= '3.7'", 57 | "version": "==3.5.0" 58 | }, 59 | "backports.zoneinfo": { 60 | "hashes": [ 61 | "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf", 62 | "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328", 63 | "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546", 64 | "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6", 65 | "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570", 66 | "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9", 67 | "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7", 68 | "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987", 69 | "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722", 70 | "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582", 71 | "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc", 72 | "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b", 73 | "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1", 74 | "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08", 75 | "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac", 76 | "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2" 77 | ], 78 | "markers": "python_version >= '3.6' and python_version < '3.9'", 79 | "version": "==0.2.1" 80 | }, 81 | "bitstring": { 82 | "hashes": [ 83 | "sha256:0de167daa6a00c9386255a7cac931b45e6e24e0ad7ea64f1f92a64ac23ad4578", 84 | "sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7", 85 | "sha256:e3e340e58900a948787a05e8c08772f1ccbe133f6f41fe3f0fa19a18a22bbf4f" 86 | ], 87 | "version": "==3.1.9" 88 | }, 89 | "bottle": { 90 | "hashes": [ 91 | "sha256:a9d73ffcbc6a1345ca2d7949638db46349f5b2b77dac65d6494d45c23628da2c", 92 | "sha256:f6b8a34fe9aa406f9813c02990db72ca69ce6a158b5b156d2c41f345016a723d" 93 | ], 94 | "version": "==0.12.19" 95 | }, 96 | "certifi": { 97 | "hashes": [ 98 | "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", 99 | "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" 100 | ], 101 | "version": "==2021.10.8" 102 | }, 103 | "cffi": { 104 | "hashes": [ 105 | "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", 106 | "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", 107 | "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", 108 | "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", 109 | "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", 110 | "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", 111 | "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", 112 | "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", 113 | "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", 114 | "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", 115 | "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", 116 | "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", 117 | "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", 118 | "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", 119 | "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", 120 | "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", 121 | "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", 122 | "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", 123 | "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", 124 | "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", 125 | "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", 126 | "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", 127 | "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", 128 | "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", 129 | "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", 130 | "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", 131 | "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", 132 | "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", 133 | "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", 134 | "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", 135 | "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", 136 | "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", 137 | "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", 138 | "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", 139 | "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", 140 | "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", 141 | "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", 142 | "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", 143 | "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", 144 | "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", 145 | "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", 146 | "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", 147 | "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", 148 | "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", 149 | "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", 150 | "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", 151 | "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", 152 | "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", 153 | "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", 154 | "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" 155 | ], 156 | "version": "==1.15.0" 157 | }, 158 | "charset-normalizer": { 159 | "hashes": [ 160 | "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", 161 | "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" 162 | ], 163 | "markers": "python_version >= '3'", 164 | "version": "==2.0.12" 165 | }, 166 | "click": { 167 | "hashes": [ 168 | "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", 169 | "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" 170 | ], 171 | "markers": "python_version >= '3.6'", 172 | "version": "==8.0.3" 173 | }, 174 | "colorama": { 175 | "hashes": [ 176 | "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", 177 | "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" 178 | ], 179 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 180 | "version": "==0.4.4" 181 | }, 182 | "cryptography": { 183 | "hashes": [ 184 | "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b", 185 | "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51", 186 | "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7", 187 | "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d", 188 | "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6", 189 | "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29", 190 | "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9", 191 | "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf", 192 | "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815", 193 | "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf", 194 | "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85", 195 | "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77", 196 | "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86", 197 | "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb", 198 | "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e", 199 | "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0", 200 | "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3", 201 | "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84", 202 | "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2", 203 | "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6" 204 | ], 205 | "markers": "python_version >= '3.6'", 206 | "version": "==36.0.2" 207 | }, 208 | "ecdsa": { 209 | "hashes": [ 210 | "sha256:87efdeb1380b50467681a8d82ed5d46af6b3b79765cf8e38f07f6a52d09b9196", 211 | "sha256:b7a29fde6d28f6817e413672ec1301dd07bddec6a4d608118fef1e32d4313c3a" 212 | ], 213 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 214 | "version": "==0.18.0b2" 215 | }, 216 | "esphome": { 217 | "hashes": [ 218 | "sha256:9f53e1f4744bd3e4649eb2c1ef50f78b7b16cd15425f77daa8b6f9f2bd01c510", 219 | "sha256:acf957c1c9cb2f5ebfc610197880c2bad3dda9828f99fd5e1204a328d5e8a836" 220 | ], 221 | "index": "pypi", 222 | "version": "==2022.3.1" 223 | }, 224 | "esphome-dashboard": { 225 | "hashes": [ 226 | "sha256:27f4f702fd23c13d02252c1cd23f988da88a16ab3db282dfedcb698b3e6b9a6e", 227 | "sha256:873ea986e6552d032bdd32b165819c6f5a9a49b1957af1a89af364afd18962b0" 228 | ], 229 | "version": "==20220309.0" 230 | }, 231 | "esptool": { 232 | "hashes": [ 233 | "sha256:9638ff11c68e621e08e7c3335d4fd9d70b2ddcf7caae778073cd8cc27be1216f" 234 | ], 235 | "version": "==3.2" 236 | }, 237 | "h11": { 238 | "hashes": [ 239 | "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06", 240 | "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442" 241 | ], 242 | "markers": "python_version >= '3.6'", 243 | "version": "==0.13.0" 244 | }, 245 | "idna": { 246 | "hashes": [ 247 | "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", 248 | "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" 249 | ], 250 | "markers": "python_version >= '3'", 251 | "version": "==3.3" 252 | }, 253 | "ifaddr": { 254 | "hashes": [ 255 | "sha256:1f9e8a6ca6f16db5a37d3356f07b6e52344f6f9f7e806d618537731669eb1a94", 256 | "sha256:d1f603952f0a71c9ab4e705754511e4e03b02565bc4cec7188ad6415ff534cd3" 257 | ], 258 | "version": "==0.1.7" 259 | }, 260 | "kconfiglib": { 261 | "hashes": [ 262 | "sha256:2205ae2628a827f7d76884bc0a2e8645d8670615402bb53f642e1e041f4a163f", 263 | "sha256:a2ee8fb06102442c45965b0596944f02c2a1517f092fa208ca307f3fd12a0a22" 264 | ], 265 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 266 | "version": "==13.7.1" 267 | }, 268 | "marshmallow": { 269 | "hashes": [ 270 | "sha256:2aaaab4f01ef4f5a011a21319af9fce17ab13bf28a026d1252adab0e035648d5", 271 | "sha256:ff79885ed43b579782f48c251d262e062bce49c65c52412458769a4fb57ac30f" 272 | ], 273 | "markers": "python_version >= '3.7'", 274 | "version": "==3.15.0" 275 | }, 276 | "noiseprotocol": { 277 | "hashes": [ 278 | "sha256:2e1a603a38439636cf0ffd8b3e8b12cee27d368a28b41be7dbe568b2abb23111", 279 | "sha256:b092a871b60f6a8f07f17950dc9f7098c8fe7d715b049bd4c24ee3752b90d645" 280 | ], 281 | "markers": "python_version ~= '3.5'", 282 | "version": "==0.3.1" 283 | }, 284 | "packaging": { 285 | "hashes": [ 286 | "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", 287 | "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" 288 | ], 289 | "markers": "python_version >= '3.6'", 290 | "version": "==21.3" 291 | }, 292 | "paho-mqtt": { 293 | "hashes": [ 294 | "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f" 295 | ], 296 | "version": "==1.6.1" 297 | }, 298 | "platformio": { 299 | "hashes": [ 300 | "sha256:aa0d1ff8a17ac1952eb45d37a84d4aa6e5d1aa65098bd1525db34be83c42c4ae" 301 | ], 302 | "version": "==5.2.5" 303 | }, 304 | "protobuf": { 305 | "hashes": [ 306 | "sha256:06e445c9352337037ee50591c64cd7f7296c8a852864588222919e9b915e34d2", 307 | "sha256:122324448da48251108d5067612a16cdf14cc5df0fb00b068b6ef828c34ae9e4", 308 | "sha256:1a96a97d7cd5ec71fc6fa5aba448f793838453a2739026ec47c258de80c0ba19", 309 | "sha256:20aefa9d310fbbae13a181fcca913aebd83e5d8e4fe48a8d8ff5bbba71483339", 310 | "sha256:219059e555be7d99fcb7c69f833690816cfbefe6229ed9cf3c464dfb5782bf6f", 311 | "sha256:28eda32fc6c746f709649ff0eae1f49aa0ea930ad5e82398b45011f50b47a423", 312 | "sha256:32b80145605f1009f08274c0a3c54b130dbe884caf6c6613e7404c5c740523c4", 313 | "sha256:340c7df4c25aff80cc6f1b3aef1f1cb1c62b97974fe72dead569713f667950f4", 314 | "sha256:34a1124bc465014dff5f233bbde2d508379258fd47626f935b2546beda428000", 315 | "sha256:44ac7fb04a4ef4d4ffe24fb111c5cb2d3c918d7946146e540d4762d622afb2ad", 316 | "sha256:4d295b31547526666189abb4788e287d10697c796b2aaeffda6d8286056ff0b6", 317 | "sha256:565c6a1d20771c099e8e68ed943375e9c2089c838bf7405bb2981b695803baee", 318 | "sha256:6332f21cd8e57e5a62f63847a5abcb96f99a5f88735fd2a1f838aa61c5efd233", 319 | "sha256:776905ee1ffea0f226d1e3a8609c5f514cd68b9c77b3840ba561878d4882a15d", 320 | "sha256:8b92e34645161bd3c69ef78075fde12b141db49a3a5c6ed67b4a663538160d07", 321 | "sha256:9457e54f44a0d41496da219b13bf1fe456ed8a5043bc1ce0ae7aaadd32a84da0", 322 | "sha256:a198454c71dec036bc1f83688e6fa134ef05f47ae1d9f5b93f8d8dc88f34e84a", 323 | "sha256:a47b041029a5177b23ff75e116d45abf71a833d48e88ab93ca44dd904a00bc7c", 324 | "sha256:a6805bfe20907cfe9087a0a3872a96dbce1087482acd4ae2e39c46d580fe1349", 325 | "sha256:bdbae2f5460dab7cb7d268a5e1e701e6b2fe280c1aa8fcf1ffad8fab45b75560", 326 | "sha256:cd90d4794b5ce5e7e539fcd7136f385a526a90697a4939f1debc5c750c329de7", 327 | "sha256:eb4a7a99fe4505aaabaa72a7e636a25e3725cd01a93bd898a9d5ffa56d54e267", 328 | "sha256:f1933ffcf71906ebb8095b69f5bde2f8dd4fdd66b37d150e5a38e684a5919938", 329 | "sha256:f42976ac862bbc2f56da5cfea4eac34659d75ec305d4d4c3de9ecb548d2c6e9e" 330 | ], 331 | "markers": "python_version >= '3.7'", 332 | "version": "==3.20.0rc2" 333 | }, 334 | "pycparser": { 335 | "hashes": [ 336 | "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", 337 | "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" 338 | ], 339 | "version": "==2.21" 340 | }, 341 | "pyelftools": { 342 | "hashes": [ 343 | "sha256:1509d30756a14a8c9a6670778144fb9a04b311751ddac019dcc0c2f28da03ccc", 344 | "sha256:53e5609cac016471d40bd88dc410cd90755942c25e58a61021cfdf7abdfeacff" 345 | ], 346 | "version": "==0.28" 347 | }, 348 | "pyparsing": { 349 | "hashes": [ 350 | "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", 351 | "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484" 352 | ], 353 | "markers": "python_version >= '3.6'", 354 | "version": "==3.0.7" 355 | }, 356 | "pyserial": { 357 | "hashes": [ 358 | "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", 359 | "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0" 360 | ], 361 | "version": "==3.5" 362 | }, 363 | "pytz-deprecation-shim": { 364 | "hashes": [ 365 | "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6", 366 | "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d" 367 | ], 368 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 369 | "version": "==0.1.0.post0" 370 | }, 371 | "pyyaml": { 372 | "hashes": [ 373 | "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", 374 | "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", 375 | "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", 376 | "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", 377 | "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", 378 | "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", 379 | "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", 380 | "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", 381 | "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", 382 | "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", 383 | "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", 384 | "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", 385 | "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", 386 | "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", 387 | "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", 388 | "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", 389 | "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", 390 | "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", 391 | "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", 392 | "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", 393 | "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", 394 | "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", 395 | "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", 396 | "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", 397 | "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", 398 | "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", 399 | "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", 400 | "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", 401 | "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", 402 | "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", 403 | "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", 404 | "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", 405 | "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" 406 | ], 407 | "markers": "python_version >= '3.6'", 408 | "version": "==6.0" 409 | }, 410 | "reedsolo": { 411 | "hashes": [ 412 | "sha256:0f5c33c5abcc51808012aa55f40d06bd15273bae2e54ad9879e41d77a73887ed", 413 | "sha256:b8b25cdc83478ccb06361a0e8fadc27b376a3dfabbb1dc6bb583a998a22c0127" 414 | ], 415 | "version": "==1.5.4" 416 | }, 417 | "requests": { 418 | "hashes": [ 419 | "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", 420 | "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" 421 | ], 422 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 423 | "version": "==2.27.1" 424 | }, 425 | "semantic-version": { 426 | "hashes": [ 427 | "sha256:abf54873553e5e07a6fd4d5f653b781f5ae41297a493666b59dcf214006a12b2", 428 | "sha256:db2504ab37902dd2c9876ece53567aa43a5b2a417fbe188097b2048fff46da3d" 429 | ], 430 | "markers": "python_version >= '2.7'", 431 | "version": "==2.9.0" 432 | }, 433 | "six": { 434 | "hashes": [ 435 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 436 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 437 | ], 438 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 439 | "version": "==1.16.0" 440 | }, 441 | "sniffio": { 442 | "hashes": [ 443 | "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663", 444 | "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de" 445 | ], 446 | "markers": "python_version >= '3.5'", 447 | "version": "==1.2.0" 448 | }, 449 | "starlette": { 450 | "hashes": [ 451 | "sha256:377d64737a0e03560cb8eaa57604afee143cea5a4996933242798a7820e64f53", 452 | "sha256:b45c6e9a617ecb5caf7e6446bd8d767b0084d6217e8e1b08187ca5191e10f097" 453 | ], 454 | "markers": "python_version >= '3.6'", 455 | "version": "==0.18.0" 456 | }, 457 | "tabulate": { 458 | "hashes": [ 459 | "sha256:d7c013fe7abbc5e491394e10fa845f8f32fe54f8dc60c6622c6cf482d25d47e4", 460 | "sha256:eb1d13f25760052e8931f2ef80aaf6045a6cceb47514db8beab24cded16f13a7" 461 | ], 462 | "version": "==0.8.9" 463 | }, 464 | "tornado": { 465 | "hashes": [ 466 | "sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb", 467 | "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c", 468 | "sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288", 469 | "sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95", 470 | "sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558", 471 | "sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe", 472 | "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791", 473 | "sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d", 474 | "sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326", 475 | "sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b", 476 | "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4", 477 | "sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c", 478 | "sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910", 479 | "sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5", 480 | "sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c", 481 | "sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0", 482 | "sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675", 483 | "sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd", 484 | "sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f", 485 | "sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c", 486 | "sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea", 487 | "sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6", 488 | "sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05", 489 | "sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd", 490 | "sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575", 491 | "sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a", 492 | "sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37", 493 | "sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795", 494 | "sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f", 495 | "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32", 496 | "sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c", 497 | "sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01", 498 | "sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4", 499 | "sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2", 500 | "sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921", 501 | "sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085", 502 | "sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df", 503 | "sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102", 504 | "sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5", 505 | "sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68", 506 | "sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5" 507 | ], 508 | "markers": "python_version >= '3.5'", 509 | "version": "==6.1" 510 | }, 511 | "typing-extensions": { 512 | "hashes": [ 513 | "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", 514 | "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2" 515 | ], 516 | "markers": "python_version < '3.10'", 517 | "version": "==4.1.1" 518 | }, 519 | "tzdata": { 520 | "hashes": [ 521 | "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9", 522 | "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3" 523 | ], 524 | "markers": "python_version >= '3.6'", 525 | "version": "==2022.1" 526 | }, 527 | "tzlocal": { 528 | "hashes": [ 529 | "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09", 530 | "sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f" 531 | ], 532 | "markers": "python_version >= '3.6'", 533 | "version": "==4.1" 534 | }, 535 | "urllib3": { 536 | "hashes": [ 537 | "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", 538 | "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" 539 | ], 540 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'", 541 | "version": "==1.26.9" 542 | }, 543 | "uvicorn": { 544 | "hashes": [ 545 | "sha256:19e2a0e96c9ac5581c01eb1a79a7d2f72bb479691acd2b8921fce48ed5b961a6", 546 | "sha256:5180f9d059611747d841a4a4c4ab675edf54c8489e97f96d0583ee90ac3bfc23" 547 | ], 548 | "markers": "python_version >= '3.7'", 549 | "version": "==0.17.6" 550 | }, 551 | "voluptuous": { 552 | "hashes": [ 553 | "sha256:4db1ac5079db9249820d49c891cb4660a6f8cae350491210abce741fabf56513" 554 | ], 555 | "version": "==0.12.2" 556 | }, 557 | "wsproto": { 558 | "hashes": [ 559 | "sha256:868776f8456997ad0d9720f7322b746bbe9193751b5b290b7f924659377c8c38", 560 | "sha256:d8345d1808dd599b5ffb352c25a367adb6157e664e140dbecba3f9bc007edb9f" 561 | ], 562 | "markers": "python_full_version >= '3.6.1'", 563 | "version": "==1.0.0" 564 | }, 565 | "zeroconf": { 566 | "hashes": [ 567 | "sha256:1c1feb71f5cf53baa5e00ff2c550edd30b2e6a20779001332115afa2ef64af72", 568 | "sha256:3cdd72184881c2d9ba163582cdd133732a3825306f048e58e4eab29f32b95b17" 569 | ], 570 | "version": "==0.38.3" 571 | } 572 | }, 573 | "develop": { 574 | "astroid": { 575 | "hashes": [ 576 | "sha256:3975a0bd5373bdce166e60c851cfcbaf21ee96de80ec518c1f4cb3e94c3fb334", 577 | "sha256:ab7f36e8a78b8e54a62028ba6beef7561db4cdb6f2a5009ecc44a6f42b5697ef" 578 | ], 579 | "markers": "python_version ~= '3.6'", 580 | "version": "==2.6.6" 581 | }, 582 | "black": { 583 | "hashes": [ 584 | "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b", 585 | "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176", 586 | "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09", 587 | "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a", 588 | "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015", 589 | "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79", 590 | "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb", 591 | "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20", 592 | "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464", 593 | "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968", 594 | "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82", 595 | "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21", 596 | "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0", 597 | "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265", 598 | "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b", 599 | "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a", 600 | "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72", 601 | "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce", 602 | "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0", 603 | "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a", 604 | "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163", 605 | "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad", 606 | "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d" 607 | ], 608 | "index": "pypi", 609 | "version": "==22.3.0" 610 | }, 611 | "click": { 612 | "hashes": [ 613 | "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", 614 | "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" 615 | ], 616 | "markers": "python_version >= '3.6'", 617 | "version": "==8.0.3" 618 | }, 619 | "flake8": { 620 | "hashes": [ 621 | "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d", 622 | "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d" 623 | ], 624 | "index": "pypi", 625 | "version": "==4.0.1" 626 | }, 627 | "isort": { 628 | "hashes": [ 629 | "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", 630 | "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" 631 | ], 632 | "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", 633 | "version": "==5.10.1" 634 | }, 635 | "lazy-object-proxy": { 636 | "hashes": [ 637 | "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7", 638 | "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a", 639 | "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c", 640 | "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc", 641 | "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f", 642 | "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09", 643 | "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442", 644 | "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e", 645 | "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029", 646 | "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61", 647 | "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb", 648 | "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0", 649 | "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35", 650 | "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42", 651 | "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1", 652 | "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad", 653 | "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443", 654 | "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd", 655 | "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9", 656 | "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148", 657 | "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38", 658 | "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55", 659 | "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36", 660 | "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a", 661 | "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b", 662 | "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44", 663 | "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6", 664 | "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69", 665 | "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4", 666 | "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84", 667 | "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de", 668 | "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28", 669 | "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c", 670 | "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1", 671 | "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8", 672 | "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b", 673 | "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb" 674 | ], 675 | "markers": "python_version >= '3.6'", 676 | "version": "==1.7.1" 677 | }, 678 | "mccabe": { 679 | "hashes": [ 680 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 681 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 682 | ], 683 | "version": "==0.6.1" 684 | }, 685 | "mypy-extensions": { 686 | "hashes": [ 687 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 688 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 689 | ], 690 | "version": "==0.4.3" 691 | }, 692 | "pathspec": { 693 | "hashes": [ 694 | "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", 695 | "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" 696 | ], 697 | "version": "==0.9.0" 698 | }, 699 | "platformdirs": { 700 | "hashes": [ 701 | "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d", 702 | "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227" 703 | ], 704 | "markers": "python_version >= '3.7'", 705 | "version": "==2.5.1" 706 | }, 707 | "pycodestyle": { 708 | "hashes": [ 709 | "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", 710 | "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f" 711 | ], 712 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 713 | "version": "==2.8.0" 714 | }, 715 | "pyflakes": { 716 | "hashes": [ 717 | "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c", 718 | "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e" 719 | ], 720 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 721 | "version": "==2.4.0" 722 | }, 723 | "pylint": { 724 | "hashes": [ 725 | "sha256:349b149e88e4357ed4f77ac3a4e61c0ab965cda293b6f4e58caf73d4b24ae551", 726 | "sha256:adc11bec00c2084bf55c81dd69e26f2793fef757547997d44b21aed038f74403" 727 | ], 728 | "index": "pypi", 729 | "version": "==3.0.0a4" 730 | }, 731 | "toml": { 732 | "hashes": [ 733 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", 734 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" 735 | ], 736 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 737 | "version": "==0.10.2" 738 | }, 739 | "tomli": { 740 | "hashes": [ 741 | "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", 742 | "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" 743 | ], 744 | "markers": "python_version < '3.11'", 745 | "version": "==2.0.1" 746 | }, 747 | "typing-extensions": { 748 | "hashes": [ 749 | "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", 750 | "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2" 751 | ], 752 | "markers": "python_version < '3.10'", 753 | "version": "==4.1.1" 754 | }, 755 | "wrapt": { 756 | "hashes": [ 757 | "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" 758 | ], 759 | "version": "==1.12.1" 760 | } 761 | } 762 | } 763 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `esphome-component-ping` 2 | 3 | A `esphome` sensor component that sends and receives `ICMP`. It implements two 4 | sensors: `loss` and `latency`. `loss` publishes packet loss rate of an ICMP 5 | session, and `latency` publishes arithmetic mean of latencies in an ICMP 6 | session. 7 | 8 | ![Visualization of ICMP latency and packet loss on ESP8266](assets/esp8266.png "Visualization of ICMP latency and packet loss on ESP8266") 9 | 10 | This is my first attempt to learn `esphome` sensor component. 11 | 12 | I have almost zero `C++` knowledge. You have been warned. 13 | 14 | ## Bugs 15 | 16 | * the target must be IP address 17 | * multiple targets are not supported 18 | 19 | ## Limitations 20 | 21 | ### The device sticks to weak `BSSID` 22 | 23 | `esphome` does not implement roaming between WiFi APs (see [Issue 732](https://github.com/esphome/feature-requests/issues/731)) 24 | This means the device does not switch to better WiFi coverage, e.g. when the 25 | closest AP is not available, the device connects to another AP available, 26 | and will not switch to the closest one even when the closest AP is back 27 | online again. This is often okay, but sometimes becomes an issue. The only 28 | workaround is to reboot the device. Be sure to add [`restart` component](https://esphome.io/components/switch/restart.html) 29 | to the device when you have multiple WiFi APs. 30 | 31 | ## Status 32 | 33 | * Works on `esp8266`, `esp32`, and `esp32c3` 34 | * the component works fine on `esp8266`. The longest uptime so far is more 35 | than 3 days. 36 | * For `esp32c3`, `framework` must be `esp-idf` 37 | (see [config/ping-esp32c3.yaml](config/ping-esp32c3.yaml)) 38 | * Requires `esphome` `2022.3.0` due to a breaking change 39 | 40 | ## Requirements 41 | 42 | * `python` 3.8 43 | * `pipenv` (only if you do not have a working `esphome` installation) 44 | 45 | ## Usage 46 | 47 | If you do not have a working `esphome`, install it. Or, install it with 48 | [`pipenv`](https://pipenv.pypa.io/en/latest/). 49 | 50 | ```console 51 | pipenv install 52 | ``` 53 | 54 | Run your shell in the python virtual environment. 55 | 56 | ```console 57 | pipenv shell 58 | ``` 59 | 60 | Copy `secrets.yaml`. You probably want to modify it, i.e. password, `SSID`, 61 | etc. 62 | 63 | ```console 64 | cp config/secrets.yaml.dist config/secrets.yaml 65 | ``` 66 | 67 | Compile `ping.yaml` in the python virtual environment. 68 | 69 | ```console 70 | esphome compile config/ping.yaml 71 | ``` 72 | 73 | ## Usage in your project 74 | 75 | See [External Components](https://esphome.io/components/external_components.html). 76 | 77 | Note that, for `esp8266`, you need to include a library 78 | [`AsyncPing`](https://github.com/akaJes/AsyncPing). Make sure your config 79 | includes it by `libraries`. 80 | 81 | Since `2021.11.4` release, `esphome` disabled `lib_ldf_mode` in `platformio`. 82 | `ESP8266WiFi`, which `AsyncPing` depends on, must be listed on `libraries`. 83 | 84 | ```yaml 85 | esphome: 86 | name: ${my_name} 87 | platform: ESP8266 88 | board: nodemcuv2 89 | libraries: 90 | - ESP8266WiFi 91 | - https://github.com/akaJes/AsyncPing#95ac7e4 92 | ``` 93 | 94 | ### With `local` 95 | 96 | Create `components` directory in your project root. 97 | 98 | ```console 99 | mkdir components 100 | ``` 101 | 102 | Clone, or `submodule`, the repository in the `components` directory. 103 | 104 | ```console 105 | cd components 106 | git clone https://github.com/trombik/esphome-component-ping.git 107 | ``` 108 | 109 | In the configuration file, set `path` in `external_components`. 110 | 111 | ```yaml 112 | external_components: 113 | - source: 114 | type: local 115 | path: ../components/esphome-component-ping/components 116 | ``` 117 | 118 | ### With `git` 119 | 120 | Add `external_components` to the configuration file. `ref` should be git `ref`; 121 | tag, branch, or commit. 122 | 123 | ```yaml 124 | external_components: 125 | - source: 126 | type: git 127 | url: https://github.com/trombik/esphome-component-ping 128 | ref: main 129 | ``` 130 | 131 | ## Example 132 | 133 | See [config/ping.yaml](config/ping.yaml). 134 | 135 | If you want to take actions on sensor values, see [config/influxdb.yaml](config/influxdb.yaml). 136 | In the example, `on_value` runs an automation (sending the value to `influxdb` 137 | whenever a value is available). Use 138 | `on_value_range` ([the documentation](https://esphome.io/components/sensor/index.html#on-value-range)) 139 | when the sensor value is above, below, or both. 140 | 141 | ## Log 142 | 143 | ### `esp8266` 144 | 145 | ```console 146 | INFO Reading configuration config/ping.yaml... 147 | INFO Starting log output from ping.local using esphome API 148 | INFO Connecting to ping.local:6053 (192.168.99.16) 149 | INFO Successfully connected to ping.local 150 | [18:30:54][I][app:105]: ESPHome version 1.19.4 compiled on Jul 19 2021, 17:42:16 151 | [18:30:54][C][wifi:484]: WiFi: 152 | [18:30:54][C][wifi:344]: SSID: [redacted] 153 | [18:30:54][C][wifi:345]: IP Address: 192.168.99.16 154 | [18:30:54][C][wifi:347]: BSSID: [redacted] 155 | [18:30:54][C][wifi:348]: Hostname: 'ping' 156 | [18:30:54][C][wifi:352]: Signal strength: -60 dB ▂▄▆█ 157 | [18:30:54][C][wifi:356]: Channel: 1 158 | [18:30:54][C][wifi:357]: Subnet: 255.255.255.0 159 | [18:30:54][C][wifi:358]: Gateway: 192.168.99.254 160 | [18:30:54][C][wifi:359]: DNS1: 192.168.99.251 161 | [18:30:54][C][wifi:360]: DNS2: (IP unset) 162 | [18:30:54][C][logger:189]: Logger: 163 | [18:30:54][C][logger:190]: Level: DEBUG 164 | [18:30:54][C][logger:191]: Log Baud Rate: 115200 165 | [18:30:54][C][logger:192]: Hardware UART: UART0 166 | [18:30:54][C][captive_portal:148]: Captive Portal: 167 | [18:30:54][C][web_server:136]: Web Server: 168 | [18:30:54][C][web_server:137]: Address: ping.local:80 169 | [18:30:54][C][ota:029]: Over-The-Air Updates: 170 | [18:30:54][C][ota:030]: Address: ping.local:8266 171 | [18:30:54][C][ota:032]: Using Password. 172 | [18:30:55][C][api:095]: API Server: 173 | [18:30:55][C][api:096]: Address: ping.local:6053 174 | [18:31:20][D][sensor:117]: 'Packet loss': Sending state 0.00000 % with 0 decimals of accuracy 175 | [18:31:20][D][sensor:117]: 'Latency': Sending state 41.00000 ms with 0 decimals of accuracy 176 | [18:31:20][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=1 ttl=55 time=48 ms 177 | [18:31:21][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=2 ttl=55 time=39 ms 178 | [18:31:22][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=3 ttl=55 time=40 ms 179 | [18:31:23][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=4 ttl=55 time=43 ms 180 | [18:31:24][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=5 ttl=55 time=41 ms 181 | [18:31:25][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=6 ttl=55 time=39 ms 182 | [18:31:26][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=7 ttl=55 time=40 ms 183 | [18:31:27][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=8 ttl=55 time=39 ms 184 | [18:31:28][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=9 ttl=55 time=39 ms 185 | [18:31:29][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=10 ttl=55 time=41 ms 186 | [18:31:30][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=11 ttl=55 time=39 ms 187 | [18:31:31][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=12 ttl=55 time=43 ms 188 | [18:31:32][I][ping:037]: 64 bytes from 156.154.100.3: icmp_seq=13 ttl=55 time=41 ms 189 | [18:31:33][I][ping:063]: packet loss: 0.0 % latency: 40 ms 190 | ``` 191 | 192 | ### `esp32` 193 | 194 | ```console 195 | [C][logger:189]: Logger: 196 | [C][logger:190]: Level: DEBUG 197 | [C][logger:191]: Log Baud Rate: 115200 198 | [C][logger:192]: Hardware UART: UART0 199 | [C][captive_portal:148]: Captive Portal: 200 | [C][web_server:136]: Web Server: 201 | [C][web_server:137]: Address: ping_sensor.local:80 202 | [C][ota:029]: Over-The-Air Updates: 203 | [C][ota:030]: Address: ping_sensor.local:3232 204 | [C][ota:032]: Using Password. 205 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=1 ttl=117 time=448 ms 206 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=2 ttl=117 time=42 ms 207 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=3 ttl=117 time=42 ms 208 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=4 ttl=117 time=39 ms 209 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=5 ttl=117 time=41 ms 210 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=6 ttl=117 time=40 ms 211 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=7 ttl=117 time=40 ms 212 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=8 ttl=117 time=43 ms 213 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=9 ttl=117 time=40 ms 214 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=10 ttl=117 time=41 ms 215 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=11 ttl=117 time=42 ms 216 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=12 ttl=117 time=42 ms 217 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=13 ttl=117 time=45 ms 218 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=14 ttl=117 time=43 ms 219 | [I][ping_esp32:063]: 64 bytes from 8.8.8.8 icmp_seq=15 ttl=117 time=40 ms 220 | [I][ping_esp32:102]: From 8.8.8.8 icmp_seq=16 timeout 221 | [I][ping_esp32:102]: From 8.8.8.8 icmp_seq=17 timeout 222 | [I][ping_esp32:084]: --- 8.8.8.8 ping statistics --- 223 | [I][ping_esp32:089]: 17 packets transmitted, 15 received, 11% packet loss, total time 3028ms avg time 68ms 224 | [D][sensor:117]: 'Packet loss': Sending state 11.00000 % with 0 decimals of accuracy 225 | [D][sensor:117]: 'Latency': Sending state 0.06800 s with 3 decimals of accuracy 226 | ``` 227 | -------------------------------------------------------------------------------- /assets/esp8266.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trombik/esphome-component-ping/724831291a20f5c75b21cd8fc1a048acc4b5bf5b/assets/esp8266.png -------------------------------------------------------------------------------- /components/ping/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trombik/esphome-component-ping/724831291a20f5c75b21cd8fc1a048acc4b5bf5b/components/ping/__init__.py -------------------------------------------------------------------------------- /components/ping/ping.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Tomoyuki Sakurai 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "esphome/components/sensor/sensor.h" 20 | 21 | namespace esphome { 22 | namespace ping { 23 | class PingSensor : public sensor::Sensor, public PollingComponent { 24 | public: 25 | Sensor *packet_loss_sensor = new Sensor(); 26 | Sensor *latency_sensor = new Sensor(); 27 | 28 | /* the sensor must be started after connecting WiFi */ 29 | float get_setup_priority() const override { return esphome::setup_priority::AFTER_WIFI; } 30 | 31 | void set_n_packet(uint32_t n) { n_packet = n; } 32 | void set_target(const std::string address) { target = address; } 33 | void set_timeout(uint32_t timeout_ms) { timeout = timeout_ms; } 34 | 35 | int get_latest_latency() { return latest_latency; } 36 | float get_latest_loss() { return latest_loss; } 37 | 38 | void set_packet_loss_sensor(sensor::Sensor *packet_loss_sensor) { packet_loss_sensor_ = packet_loss_sensor; } 39 | void set_latency_sensor(sensor::Sensor *latency_sensor) { latency_sensor_ = latency_sensor; } 40 | 41 | private: 42 | protected: 43 | int latest_latency = -1; 44 | int timeout = 1000; 45 | int n_packet = 13; 46 | float latest_loss = -1; 47 | std::string target = "8.8.8.8"; 48 | sensor::Sensor *packet_loss_sensor_{nullptr}; 49 | sensor::Sensor *latency_sensor_{nullptr}; 50 | 51 | void set_latest_loss(float f) { latest_loss = f; } 52 | void set_latest_latency(int i) { latest_latency = i; } 53 | }; 54 | 55 | } // namespace ping 56 | } // namespace esphome 57 | -------------------------------------------------------------------------------- /components/ping/ping_esp32.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Tomoyuki Sakurai 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #pragma once 18 | 19 | #if defined(USE_ESP32) 20 | #include "lwip/inet.h" 21 | #include "lwip/sockets.h" 22 | #include "ping_sock.h" 23 | #include "esp_err.h" 24 | 25 | #include "esphome/components/sensor/sensor.h" 26 | 27 | #define TAG "ping_esp32" 28 | 29 | namespace esphome { 30 | namespace ping { 31 | class PingSensorESP32 : public PingSensor { 32 | public: 33 | void setup() override { init_ping(); } 34 | 35 | void update() override { 36 | float loss; 37 | int latency_ms; 38 | esp_err_t err = ESP_FAIL; 39 | 40 | loss = this->get_latest_loss(); 41 | latency_ms = this->get_latest_latency(); 42 | 43 | if (loss >= 0 && this->packet_loss_sensor_ != nullptr) { 44 | packet_loss_sensor_->publish_state(loss); 45 | } 46 | if (latency_ms >= 0 && this->latency_sensor_ != nullptr) { 47 | latency_sensor_->publish_state((float) latency_ms / 1000); 48 | } 49 | err = esp_ping_new_session(&ping_config, &cbs, &ping); 50 | if (err != ESP_OK) { 51 | ESP_LOGE(TAG, "esp_ping_new_session: %s", esp_err_to_name(err)); 52 | } 53 | err = esp_ping_start(ping); 54 | if (err != ESP_OK) { 55 | ESP_LOGE(TAG, "esp_ping_start: %s", esp_err_to_name(err)); 56 | } 57 | } 58 | 59 | private: 60 | static void cb_ping_on_ping_success(esp_ping_handle_t hdl, void *context) { 61 | reinterpret_cast(context)->cmd_ping_on_ping_success(hdl); 62 | } 63 | 64 | static void cb_cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *context) { 65 | reinterpret_cast(context)->cmd_ping_on_ping_end(hdl); 66 | } 67 | 68 | static void cb_cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *context) { 69 | reinterpret_cast(context)->cmd_ping_on_ping_timeout(hdl); 70 | } 71 | 72 | void cmd_ping_on_ping_success(esp_ping_handle_t hdl) { 73 | uint8_t ttl; 74 | uint16_t seqno; 75 | uint32_t elapsed_time, recv_len; 76 | ip_addr_t target_addr; 77 | esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); 78 | esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); 79 | esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); 80 | esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); 81 | esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); 82 | ESP_LOGV(TAG, "%d bytes from %s icmp_seq=%d ttl=%d time=%d ms", recv_len, ipaddr_ntoa((ip_addr_t *) &target_addr), 83 | seqno, ttl, elapsed_time); 84 | this->incr_total_success_time(elapsed_time); 85 | } 86 | 87 | void cmd_ping_on_ping_end(esp_ping_handle_t hdl) { 88 | ip_addr_t target_addr; 89 | uint32_t transmitted; 90 | uint32_t received; 91 | uint32_t total_time_ms; 92 | int mean = 0; 93 | 94 | esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); 95 | esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received)); 96 | esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); 97 | esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); 98 | uint32_t loss = (uint32_t)((1 - ((float) received) / transmitted) * 100); 99 | if (received != 0) { 100 | mean = total_success_time / received; 101 | } 102 | 103 | if (IP_IS_V4(&target_addr)) { 104 | ESP_LOGD(TAG, "--- %s ping statistics ---", inet_ntoa(*ip_2_ip4(&target_addr))); 105 | } else { 106 | ESP_LOGD(TAG, "--- %s ping statistics ---", inet6_ntoa(*ip_2_ip6(&target_addr))); 107 | } 108 | ESP_LOGD(TAG, "%d packets transmitted, %d received, %d%% packet loss, total time %dms avg time %dms", transmitted, 109 | received, loss, total_time_ms, mean); 110 | 111 | this->set_latest_loss(loss); 112 | this->set_latest_latency(mean); 113 | this->reset(); 114 | esp_ping_delete_session(hdl); 115 | } 116 | 117 | void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl) { 118 | uint16_t seqno; 119 | ip_addr_t target_addr; 120 | esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); 121 | esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); 122 | ESP_LOGV(TAG, "From %s icmp_seq=%d timeout", ipaddr_ntoa((ip_addr_t *) &target_addr), seqno); 123 | } 124 | 125 | protected: 126 | std::string tag = "ping_esp32"; 127 | esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG(); 128 | esp_ping_handle_t ping; 129 | esp_ping_callbacks_t cbs; 130 | 131 | void init_ping() { 132 | ip_addr_t target_addr; 133 | int err; 134 | 135 | memset(&target_addr, 0, sizeof(target_addr)); 136 | err = inet_pton(AF_INET, target.c_str(), &target_addr); 137 | if (err == 0) { 138 | ESP_LOGE(tag.c_str(), "invalid address: `%s`", target.c_str()); 139 | this->status_set_warning(); 140 | return; 141 | } else if (err < 0) { 142 | ESP_LOGE(tag.c_str(), "inet_pton(): %s", esp_err_to_name(errno)); 143 | this->status_set_warning(); 144 | return; 145 | } 146 | 147 | ping_config.target_addr = target_addr; 148 | ping_config.count = n_packet; 149 | 150 | cbs.on_ping_success = PingSensorESP32::cb_ping_on_ping_success; 151 | cbs.on_ping_timeout = PingSensorESP32::cb_cmd_ping_on_ping_timeout; 152 | cbs.on_ping_end = PingSensorESP32::cb_cmd_ping_on_ping_end; 153 | cbs.cb_args = this; 154 | } 155 | int total_success_time; 156 | void reset() { total_success_time = 0; } 157 | void incr_total_success_time(int time) { total_success_time += time; } 158 | }; 159 | 160 | } // namespace ping 161 | } // namespace esphome 162 | #endif 163 | -------------------------------------------------------------------------------- /components/ping/ping_esp8266.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Tomoyuki Sakurai 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #pragma once 18 | 19 | #if defined(USE_ESP8266) 20 | #include "AsyncPing.h" 21 | 22 | #include "esphome/components/sensor/sensor.h" 23 | 24 | #define EACH_RESULT true 25 | #define END false 26 | #define TAG "ping" 27 | 28 | namespace esphome { 29 | namespace ping { 30 | class PingSensorESP8266 : public PingSensor { 31 | public: 32 | void setup() override { 33 | ping.on(EACH_RESULT, [this](const AsyncPingResponse &response) { 34 | if (response.answer) { 35 | ESP_LOGV(TAG, "%d bytes from %s: icmp_seq=%d ttl=%d time=%d ms", response.size, target.c_str(), 36 | response.icmp_seq, response.ttl, response.time); 37 | 38 | this->incr_total_success_time(response.time); 39 | } else { 40 | ESP_LOGV(TAG, "no reply from %s", target.c_str()); 41 | } 42 | return false; 43 | }); 44 | 45 | /* at the end, set the result */ 46 | ping.on(END, [this](const AsyncPingResponse &response) { 47 | float loss = 0; 48 | int total_failed_count = response.total_sent - response.total_recv; 49 | if (response.total_sent != 0) { 50 | loss = (float) total_failed_count / response.total_sent; 51 | } 52 | 53 | int mean = 0; 54 | if (response.total_recv != 0) { 55 | mean = total_success_time / response.total_recv; 56 | } 57 | 58 | this->set_latest_loss(loss * 100); 59 | this->set_latest_latency(mean); 60 | 61 | ESP_LOGD(TAG, "packet loss: %0.1f %% latency: %d ms", loss * 100, mean); 62 | this->reset(); 63 | return true; 64 | }); 65 | ping.begin(target.c_str(), n_packet, timeout); 66 | } 67 | 68 | void update() override { 69 | float loss; 70 | int latency_ms; 71 | 72 | loss = this->get_latest_loss(); 73 | latency_ms = this->get_latest_latency(); 74 | 75 | if (loss >= 0 && this->packet_loss_sensor_ != nullptr) { 76 | packet_loss_sensor_->publish_state(loss); 77 | } 78 | if (latency_ms >= 0 && this->latency_sensor_ != nullptr) { 79 | latency_sensor_->publish_state((float) latency_ms / 1000); 80 | } 81 | ping.begin(target.c_str(), n_packet, timeout); 82 | } 83 | 84 | private: 85 | protected: 86 | int total_success_time; 87 | void reset() { total_success_time = 0; } 88 | void incr_total_success_time(int time) { total_success_time += time; } 89 | 90 | AsyncPing ping; 91 | }; 92 | 93 | } // namespace ping 94 | } // namespace esphome 95 | #endif 96 | -------------------------------------------------------------------------------- /components/ping/ping_sock.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #if defined(USE_ESP32) 16 | 17 | #include 18 | #include 19 | #include 20 | #include "freertos/FreeRTOS.h" 21 | #include "freertos/task.h" 22 | #include "lwip/opt.h" 23 | #include "lwip/init.h" 24 | #include "lwip/mem.h" 25 | #include "lwip/icmp.h" 26 | #include "lwip/netif.h" 27 | #include "lwip/sys.h" 28 | #include "lwip/timeouts.h" 29 | #include "lwip/inet.h" 30 | #include "lwip/inet_chksum.h" 31 | #include "lwip/ip.h" 32 | #include "lwip/netdb.h" 33 | #include "lwip/sockets.h" 34 | #include "esp_log.h" 35 | #include "ping_sock.h" 36 | 37 | const static char *TAG = "ping_sock"; 38 | 39 | #define PING_CHECK(a, str, goto_tag, ret_value, ...) \ 40 | do \ 41 | { \ 42 | if (!(a)) \ 43 | { \ 44 | ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ 45 | ret = ret_value; \ 46 | goto goto_tag; \ 47 | } \ 48 | } while (0) 49 | 50 | #define PING_TIME_DIFF_MS(_end, _start) ((uint32_t)(((_end).tv_sec - (_start).tv_sec) * 1000 + \ 51 | ((_end).tv_usec - (_start).tv_usec) / 1000)) 52 | 53 | #define PING_CHECK_START_TIMEOUT_MS (1000) 54 | 55 | #define PING_FLAGS_INIT (1 << 0) 56 | #define PING_FLAGS_START (1 << 1) 57 | 58 | typedef struct { 59 | int sock; 60 | struct sockaddr_storage target_addr; 61 | TaskHandle_t ping_task_hdl; 62 | struct icmp_echo_hdr *packet_hdr; 63 | ip_addr_t recv_addr; 64 | uint32_t recv_len; 65 | uint32_t icmp_pkt_size; 66 | uint32_t count; 67 | uint32_t transmitted; 68 | uint32_t received; 69 | uint32_t interval_ms; 70 | uint32_t elapsed_time_ms; 71 | uint32_t total_time_ms; 72 | uint8_t ttl; 73 | uint32_t flags; 74 | void (*on_ping_success)(esp_ping_handle_t hdl, void *args); 75 | void (*on_ping_timeout)(esp_ping_handle_t hdl, void *args); 76 | void (*on_ping_end)(esp_ping_handle_t hdl, void *args); 77 | void *cb_args; 78 | } esp_ping_t; 79 | 80 | static esp_err_t esp_ping_send(esp_ping_t *ep) 81 | { 82 | esp_err_t ret = ESP_OK; 83 | ep->packet_hdr->seqno++; 84 | /* generate checksum since "seqno" has changed */ 85 | ep->packet_hdr->chksum = 0; 86 | if (ep->packet_hdr->type == ICMP_ECHO) { 87 | ep->packet_hdr->chksum = inet_chksum(ep->packet_hdr, ep->icmp_pkt_size); 88 | } 89 | 90 | ssize_t sent = sendto(ep->sock, ep->packet_hdr, ep->icmp_pkt_size, 0, 91 | (struct sockaddr *)&ep->target_addr, sizeof(ep->target_addr)); 92 | 93 | if (sent != (ssize_t)ep->icmp_pkt_size) { 94 | int opt_val; 95 | socklen_t opt_len = sizeof(opt_val); 96 | getsockopt(ep->sock, SOL_SOCKET, SO_ERROR, &opt_val, &opt_len); 97 | ESP_LOGE(TAG, "send error=%d", opt_val); 98 | ret = ESP_FAIL; 99 | } else { 100 | ep->transmitted++; 101 | } 102 | return ret; 103 | } 104 | 105 | static int esp_ping_receive(esp_ping_t *ep) 106 | { 107 | char buf[64]; // 64 bytes are enough to cover IP header and ICMP header 108 | int len = 0; 109 | struct sockaddr_storage from; 110 | int fromlen = sizeof(from); 111 | uint16_t data_head = 0; 112 | 113 | while ((len = recvfrom(ep->sock, buf, sizeof(buf), 0, (struct sockaddr *)&from, (socklen_t *)&fromlen)) > 0) { 114 | if (from.ss_family == AF_INET) { 115 | // IPv4 116 | struct sockaddr_in *from4 = (struct sockaddr_in *)&from; 117 | inet_addr_to_ip4addr(ip_2_ip4(&ep->recv_addr), &from4->sin_addr); 118 | IP_SET_TYPE_VAL(ep->recv_addr, IPADDR_TYPE_V4); 119 | data_head = (uint16_t)(sizeof(struct ip_hdr) + sizeof(struct icmp_echo_hdr)); 120 | } 121 | #if CONFIG_LWIP_IPV6 122 | else { 123 | // IPv6 124 | struct sockaddr_in6 *from6 = (struct sockaddr_in6 *)&from; 125 | inet6_addr_to_ip6addr(ip_2_ip6(&ep->recv_addr), &from6->sin6_addr); 126 | IP_SET_TYPE_VAL(ep->recv_addr, IPADDR_TYPE_V6); 127 | data_head = (uint16_t)(sizeof(struct ip6_hdr) + sizeof(struct icmp6_echo_hdr)); 128 | } 129 | #endif 130 | if (len >= data_head) { 131 | if (IP_IS_V4_VAL(ep->recv_addr)) { // Currently we process IPv4 132 | struct ip_hdr *iphdr = (struct ip_hdr *)buf; 133 | struct icmp_echo_hdr *iecho = (struct icmp_echo_hdr *)(buf + (IPH_HL(iphdr) * 4)); 134 | if ((iecho->id == ep->packet_hdr->id) && (iecho->seqno == ep->packet_hdr->seqno)) { 135 | ep->received++; 136 | ep->ttl = iphdr->_ttl; 137 | ep->recv_len = lwip_ntohs(IPH_LEN(iphdr)) - data_head; // The data portion of ICMP 138 | return len; 139 | } 140 | } 141 | #if CONFIG_LWIP_IPV6 142 | else if (IP_IS_V6_VAL(ep->recv_addr)) { // Currently we process IPv6 143 | struct ip6_hdr *iphdr = (struct ip6_hdr *)buf; 144 | struct icmp6_echo_hdr *iecho6 = (struct icmp6_echo_hdr *)(buf + sizeof(struct ip6_hdr)); // IPv6 head length is 40 145 | if ((iecho6->id == ep->packet_hdr->id) && (iecho6->seqno == ep->packet_hdr->seqno)) { 146 | ep->received++; 147 | ep->recv_len = IP6H_PLEN(iphdr) - sizeof(struct icmp6_echo_hdr); //The data portion of ICMPv6 148 | return len; 149 | } 150 | } 151 | #endif 152 | } 153 | fromlen = sizeof(from); 154 | } 155 | // if timeout, len will be -1 156 | return len; 157 | } 158 | 159 | static void esp_ping_thread(void *args) 160 | { 161 | esp_ping_t *ep = (esp_ping_t *)(args); 162 | TickType_t last_wake; 163 | struct timeval start_time, end_time; 164 | int recv_ret; 165 | 166 | while (1) { 167 | /* wait for ping start signal */ 168 | if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(PING_CHECK_START_TIMEOUT_MS))) { 169 | /* initialize runtime statistics */ 170 | ep->packet_hdr->seqno = 0; 171 | ep->transmitted = 0; 172 | ep->received = 0; 173 | ep->total_time_ms = 0; 174 | 175 | last_wake = xTaskGetTickCount(); 176 | while ((ep->flags & PING_FLAGS_START) && ((ep->count == 0) || (ep->packet_hdr->seqno < ep->count))) { 177 | esp_ping_send(ep); 178 | gettimeofday(&start_time, NULL); 179 | recv_ret = esp_ping_receive(ep); 180 | gettimeofday(&end_time, NULL); 181 | ep->elapsed_time_ms = PING_TIME_DIFF_MS(end_time, start_time); 182 | ep->total_time_ms += ep->elapsed_time_ms; 183 | if (recv_ret >= 0) { 184 | if (ep->on_ping_success) { 185 | ep->on_ping_success((esp_ping_handle_t)ep, ep->cb_args); 186 | } 187 | } else { 188 | if (ep->on_ping_timeout) { 189 | ep->on_ping_timeout((esp_ping_handle_t)ep, ep->cb_args); 190 | } 191 | } 192 | if (pdMS_TO_TICKS(ep->interval_ms)) { 193 | vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(ep->interval_ms)); // to get a more accurate delay 194 | } 195 | } 196 | /* batch of ping operations finished */ 197 | if (ep->on_ping_end) { 198 | ep->on_ping_end((esp_ping_handle_t)ep, ep->cb_args); 199 | } 200 | } else { 201 | // check if ping has been de-initialized 202 | if (!(ep->flags & PING_FLAGS_INIT)) { 203 | break; 204 | } 205 | } 206 | } 207 | /* before exit task, free all resources */ 208 | if (ep->packet_hdr) { 209 | free(ep->packet_hdr); 210 | } 211 | if (ep->sock > 0) { 212 | close(ep->sock); 213 | } 214 | free(ep); 215 | vTaskDelete(NULL); 216 | } 217 | 218 | esp_err_t esp_ping_new_session(const esp_ping_config_t *config, const esp_ping_callbacks_t *cbs, esp_ping_handle_t *hdl_out) 219 | { 220 | esp_err_t ret = ESP_FAIL; 221 | esp_ping_t *ep = NULL; 222 | PING_CHECK(config, "ping config can't be null", err, ESP_ERR_INVALID_ARG); 223 | PING_CHECK(hdl_out, "ping handle can't be null", err, ESP_ERR_INVALID_ARG); 224 | 225 | ep = mem_calloc(1, sizeof(esp_ping_t)); 226 | PING_CHECK(ep, "no memory for esp_ping object", err, ESP_ERR_NO_MEM); 227 | 228 | /* set INIT flag, so that ping task won't exit (must set before create ping task) */ 229 | ep->flags |= PING_FLAGS_INIT; 230 | 231 | /* create ping thread */ 232 | BaseType_t xReturned = xTaskCreate(esp_ping_thread, "ping", config->task_stack_size, ep, 233 | config->task_prio, &ep->ping_task_hdl); 234 | PING_CHECK(xReturned == pdTRUE, "create ping task failed", err, ESP_ERR_NO_MEM); 235 | 236 | /* callback functions */ 237 | if (cbs) { 238 | ep->cb_args = cbs->cb_args; 239 | ep->on_ping_end = cbs->on_ping_end; 240 | ep->on_ping_timeout = cbs->on_ping_timeout; 241 | ep->on_ping_success = cbs->on_ping_success; 242 | } 243 | /* set parameters for ping */ 244 | ep->recv_addr = config->target_addr; 245 | ep->count = config->count; 246 | ep->interval_ms = config->interval_ms; 247 | ep->icmp_pkt_size = sizeof(struct icmp_echo_hdr) + config->data_size; 248 | ep->packet_hdr = mem_calloc(1, ep->icmp_pkt_size); 249 | PING_CHECK(ep->packet_hdr, "no memory for echo packet", err, ESP_ERR_NO_MEM); 250 | /* set ICMP type and code field */ 251 | ep->packet_hdr->code = 0; 252 | /* ping id should be unique, treat task handle as ping ID */ 253 | ep->packet_hdr->id = ((uint32_t)ep->ping_task_hdl) & 0xFFFF; 254 | /* fill the additional data buffer with some data */ 255 | char *d = (char *)(ep->packet_hdr) + sizeof(struct icmp_echo_hdr); 256 | for (uint32_t i = 0; i < config->data_size; i++) { 257 | d[i] = 'A' + i; 258 | } 259 | 260 | /* create socket */ 261 | if (IP_IS_V4(&config->target_addr) 262 | #if CONFIG_LWIP_IPV6 263 | || ip6_addr_isipv4mappedipv6(ip_2_ip6(&config->target_addr)) 264 | #endif 265 | ) { 266 | ep->sock = socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP); 267 | } 268 | #if CONFIG_LWIP_IPV6 269 | else { 270 | ep->sock = socket(AF_INET6, SOCK_RAW, IP6_NEXTH_ICMP6); 271 | } 272 | #endif 273 | PING_CHECK(ep->sock > 0, "create socket failed: %d", err, ESP_FAIL, ep->sock); 274 | #if 0 275 | /* disable code that does not build with esphome. 276 | * ifreq does not exist in esp-idf 3.x, which esphome uses */ 277 | 278 | /* set if index */ 279 | if(config->interface) { 280 | struct ifreq iface; 281 | if(netif_index_to_name(config->interface, iface.ifr_name) == NULL) { 282 | ESP_LOGE(TAG, "fail to find interface name with netif index %d", config->interface); 283 | goto err; 284 | } 285 | if(setsockopt(ep->sock, SOL_SOCKET, SO_BINDTODEVICE, &iface, sizeof(iface)) != 0) { 286 | ESP_LOGE(TAG, "fail to setsockopt SO_BINDTODEVICE"); 287 | goto err; 288 | } 289 | } 290 | #endif 291 | struct timeval timeout; 292 | timeout.tv_sec = config->timeout_ms / 1000; 293 | timeout.tv_usec = (config->timeout_ms % 1000) * 1000; 294 | /* set receive timeout */ 295 | setsockopt(ep->sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); 296 | 297 | /* set tos */ 298 | setsockopt(ep->sock, IPPROTO_IP, IP_TOS, &config->tos, sizeof(config->tos)); 299 | 300 | /* set socket address */ 301 | if (IP_IS_V4(&config->target_addr)) { 302 | struct sockaddr_in *to4 = (struct sockaddr_in *)&ep->target_addr; 303 | to4->sin_family = AF_INET; 304 | inet_addr_from_ip4addr(&to4->sin_addr, ip_2_ip4(&config->target_addr)); 305 | ep->packet_hdr->type = ICMP_ECHO; 306 | } 307 | #if CONFIG_LWIP_IPV6 308 | if (IP_IS_V6(&config->target_addr)) { 309 | struct sockaddr_in6 *to6 = (struct sockaddr_in6 *)&ep->target_addr; 310 | to6->sin6_family = AF_INET6; 311 | inet6_addr_from_ip6addr(&to6->sin6_addr, ip_2_ip6(&config->target_addr)); 312 | ep->packet_hdr->type = ICMP6_TYPE_EREQ; 313 | } 314 | #endif 315 | /* return ping handle to user */ 316 | *hdl_out = (esp_ping_handle_t)ep; 317 | return ESP_OK; 318 | err: 319 | if (ep) { 320 | if (ep->sock > 0) { 321 | close(ep->sock); 322 | } 323 | if (ep->packet_hdr) { 324 | free(ep->packet_hdr); 325 | } 326 | if (ep->ping_task_hdl) { 327 | vTaskDelete(ep->ping_task_hdl); 328 | } 329 | free(ep); 330 | } 331 | return ret; 332 | } 333 | 334 | esp_err_t esp_ping_delete_session(esp_ping_handle_t hdl) 335 | { 336 | esp_err_t ret = ESP_OK; 337 | esp_ping_t *ep = (esp_ping_t *)hdl; 338 | PING_CHECK(ep, "ping handle can't be null", err, ESP_ERR_INVALID_ARG); 339 | /* reset init flags, then ping task will exit */ 340 | ep->flags &= ~PING_FLAGS_INIT; 341 | return ESP_OK; 342 | err: 343 | return ret; 344 | } 345 | 346 | esp_err_t esp_ping_start(esp_ping_handle_t hdl) 347 | { 348 | esp_err_t ret = ESP_OK; 349 | esp_ping_t *ep = (esp_ping_t *)hdl; 350 | PING_CHECK(ep, "ping handle can't be null", err, ESP_ERR_INVALID_ARG); 351 | ep->flags |= PING_FLAGS_START; 352 | xTaskNotifyGive(ep->ping_task_hdl); 353 | return ESP_OK; 354 | err: 355 | return ret; 356 | } 357 | 358 | esp_err_t esp_ping_stop(esp_ping_handle_t hdl) 359 | { 360 | esp_err_t ret = ESP_OK; 361 | esp_ping_t *ep = (esp_ping_t *)hdl; 362 | PING_CHECK(ep, "ping handle can't be null", err, ESP_ERR_INVALID_ARG); 363 | ep->flags &= ~PING_FLAGS_START; 364 | return ESP_OK; 365 | err: 366 | return ret; 367 | } 368 | 369 | esp_err_t esp_ping_get_profile(esp_ping_handle_t hdl, esp_ping_profile_t profile, void *data, uint32_t size) 370 | { 371 | esp_err_t ret = ESP_OK; 372 | esp_ping_t *ep = (esp_ping_t *)hdl; 373 | const void *from = NULL; 374 | uint32_t copy_size = 0; 375 | PING_CHECK(ep, "ping handle can't be null", err, ESP_ERR_INVALID_ARG); 376 | PING_CHECK(data, "profile data can't be null", err, ESP_ERR_INVALID_ARG); 377 | switch (profile) { 378 | case ESP_PING_PROF_SEQNO: 379 | from = &ep->packet_hdr->seqno; 380 | copy_size = sizeof(ep->packet_hdr->seqno); 381 | break; 382 | case ESP_PING_PROF_TTL: 383 | from = &ep->ttl; 384 | copy_size = sizeof(ep->ttl); 385 | break; 386 | case ESP_PING_PROF_REQUEST: 387 | from = &ep->transmitted; 388 | copy_size = sizeof(ep->transmitted); 389 | break; 390 | case ESP_PING_PROF_REPLY: 391 | from = &ep->received; 392 | copy_size = sizeof(ep->received); 393 | break; 394 | case ESP_PING_PROF_IPADDR: 395 | from = &ep->recv_addr; 396 | copy_size = sizeof(ep->recv_addr); 397 | break; 398 | case ESP_PING_PROF_SIZE: 399 | from = &ep->recv_len; 400 | copy_size = sizeof(ep->recv_len); 401 | break; 402 | case ESP_PING_PROF_TIMEGAP: 403 | from = &ep->elapsed_time_ms; 404 | copy_size = sizeof(ep->elapsed_time_ms); 405 | break; 406 | case ESP_PING_PROF_DURATION: 407 | from = &ep->total_time_ms; 408 | copy_size = sizeof(ep->total_time_ms); 409 | break; 410 | default: 411 | PING_CHECK(false, "unknow profile: %d", err, ESP_ERR_INVALID_ARG, profile); 412 | break; 413 | } 414 | PING_CHECK(size >= copy_size, "unmatched data size for profile %d", err, ESP_ERR_INVALID_SIZE, profile); 415 | memcpy(data, from, copy_size); 416 | return ESP_OK; 417 | err: 418 | return ret; 419 | } 420 | #endif 421 | -------------------------------------------------------------------------------- /components/ping/ping_sock.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #if defined(USE_ESP32) 16 | 17 | #pragma once 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #include 24 | #include "esp_err.h" 25 | #include "lwip/ip_addr.h" 26 | 27 | /** 28 | * @brief Type of "ping" session handle 29 | * 30 | */ 31 | typedef void *esp_ping_handle_t; 32 | 33 | /** 34 | * @brief Type of "ping" callback functions 35 | * 36 | */ 37 | typedef struct { 38 | /** 39 | * @brief arguments for callback functions 40 | * 41 | */ 42 | void *cb_args; 43 | 44 | /** 45 | * @brief Invoked by internal ping thread when received ICMP echo reply packet 46 | * 47 | */ 48 | void (*on_ping_success)(esp_ping_handle_t hdl, void *args); 49 | 50 | /** 51 | * @brief Invoked by internal ping thread when receive ICMP echo reply packet timeout 52 | * 53 | */ 54 | void (*on_ping_timeout)(esp_ping_handle_t hdl, void *args); 55 | 56 | /** 57 | * @brief Invoked by internal ping thread when a ping session is finished 58 | * 59 | */ 60 | void (*on_ping_end)(esp_ping_handle_t hdl, void *args); 61 | } esp_ping_callbacks_t; 62 | 63 | /** 64 | * @brief Type of "ping" configuration 65 | * 66 | */ 67 | typedef struct { 68 | uint32_t count; /*!< A "ping" session contains count procedures */ 69 | uint32_t interval_ms; /*!< Milliseconds between each ping procedure */ 70 | uint32_t timeout_ms; /*!< Timeout value (in milliseconds) of each ping procedure */ 71 | uint32_t data_size; /*!< Size of the data next to ICMP packet header */ 72 | uint8_t tos; /*!< Type of Service, a field specified in the IP header */ 73 | ip_addr_t target_addr; /*!< Target IP address, either IPv4 or IPv6 */ 74 | uint32_t task_stack_size; /*!< Stack size of internal ping task */ 75 | uint32_t task_prio; /*!< Priority of internal ping task */ 76 | uint32_t interface; /*!< Netif index, interface=0 means NETIF_NO_INDEX*/ 77 | } esp_ping_config_t; 78 | 79 | /** 80 | * @brief Default ping configuration 81 | * 82 | */ 83 | #define ESP_PING_DEFAULT_CONFIG() \ 84 | { \ 85 | .count = 5, \ 86 | .interval_ms = 1000, \ 87 | .timeout_ms = 1000, \ 88 | .data_size = 64, \ 89 | .tos = 0, \ 90 | .target_addr = *(IP_ANY_TYPE), \ 91 | .task_stack_size = 2048, \ 92 | .task_prio = 2, \ 93 | .interface = 0,\ 94 | } 95 | 96 | #define ESP_PING_COUNT_INFINITE (0) /*!< Set ping count to zero will ping target infinitely */ 97 | 98 | /** 99 | * @brief Profile of ping session 100 | * 101 | */ 102 | typedef enum { 103 | ESP_PING_PROF_SEQNO, /*!< Sequence number of a ping procedure */ 104 | ESP_PING_PROF_TTL, /*!< Time to live of a ping procedure */ 105 | ESP_PING_PROF_REQUEST, /*!< Number of request packets sent out */ 106 | ESP_PING_PROF_REPLY, /*!< Number of reply packets received */ 107 | ESP_PING_PROF_IPADDR, /*!< IP address of replied target */ 108 | ESP_PING_PROF_SIZE, /*!< Size of received packet */ 109 | ESP_PING_PROF_TIMEGAP, /*!< Elapsed time between request and reply packet */ 110 | ESP_PING_PROF_DURATION /*!< Elapsed time of the whole ping session */ 111 | } esp_ping_profile_t; 112 | 113 | /** 114 | * @brief Create a ping session 115 | * 116 | * @param config ping configuration 117 | * @param cbs a bunch of callback functions invoked by internal ping task 118 | * @param hdl_out handle of ping session 119 | * @return 120 | * - ESP_ERR_INVALID_ARG: invalid parameters (e.g. configuration is null, etc) 121 | * - ESP_ERR_NO_MEM: out of memory 122 | * - ESP_FAIL: other internal error (e.g. socket error) 123 | * - ESP_OK: create ping session successfully, user can take the ping handle to do follow-on jobs 124 | */ 125 | esp_err_t esp_ping_new_session(const esp_ping_config_t *config, const esp_ping_callbacks_t *cbs, esp_ping_handle_t *hdl_out); 126 | 127 | /** 128 | * @brief Delete a ping session 129 | * 130 | * @param hdl handle of ping session 131 | * @return 132 | * - ESP_ERR_INVALID_ARG: invalid parameters (e.g. ping handle is null, etc) 133 | * - ESP_OK: delete ping session successfully 134 | */ 135 | esp_err_t esp_ping_delete_session(esp_ping_handle_t hdl); 136 | 137 | /** 138 | * @brief Start the ping session 139 | * 140 | * @param hdl handle of ping session 141 | * @return 142 | * - ESP_ERR_INVALID_ARG: invalid parameters (e.g. ping handle is null, etc) 143 | * - ESP_OK: start ping session successfully 144 | */ 145 | esp_err_t esp_ping_start(esp_ping_handle_t hdl); 146 | 147 | /** 148 | * @brief Stop the ping session 149 | * 150 | * @param hdl handle of ping session 151 | * @return 152 | * - ESP_ERR_INVALID_ARG: invalid parameters (e.g. ping handle is null, etc) 153 | * - ESP_OK: stop ping session successfully 154 | */ 155 | esp_err_t esp_ping_stop(esp_ping_handle_t hdl); 156 | 157 | /** 158 | * @brief Get runtime profile of ping session 159 | * 160 | * @param hdl handle of ping session 161 | * @param profile type of profile 162 | * @param data profile data 163 | * @param size profile data size 164 | * @return 165 | * - ESP_ERR_INVALID_ARG: invalid parameters (e.g. ping handle is null, etc) 166 | * - ESP_ERR_INVALID_SIZE: the actual profile data size doesn't match the "size" parameter 167 | * - ESP_OK: get profile successfully 168 | */ 169 | esp_err_t esp_ping_get_profile(esp_ping_handle_t hdl, esp_ping_profile_t profile, void *data, uint32_t size); 170 | 171 | #ifdef __cplusplus 172 | } 173 | #endif 174 | 175 | #endif 176 | -------------------------------------------------------------------------------- /components/ping/sensor.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Tomoyuki Sakurai 2 | # 3 | # Permission to use, copy, modify, and distribute this software for any 4 | # purpose with or without fee is hereby granted, provided that the above 5 | # copyright notice and this permission notice appear in all copies. 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | import esphome.codegen as cg 16 | import esphome.config_validation as cv 17 | from esphome.components import sensor 18 | from esphome.const import ( 19 | CONF_ID, 20 | CONF_IP_ADDRESS, 21 | CONF_NUM_ATTEMPTS, 22 | CONF_TIMEOUT, 23 | STATE_CLASS_MEASUREMENT, 24 | UNIT_PERCENT, 25 | UNIT_SECOND, 26 | ICON_EMPTY, 27 | DEVICE_CLASS_EMPTY, 28 | ) 29 | from esphome.core import CORE 30 | 31 | DEPENDENCIES = ["network"] 32 | 33 | ping_ns = cg.esphome_ns.namespace("ping") 34 | 35 | if CORE.is_esp8266: 36 | PingSensor = ping_ns.class_("PingSensorESP8266", sensor.Sensor, cg.PollingComponent) 37 | elif CORE.is_esp32: 38 | PingSensor = ping_ns.class_("PingSensorESP32", sensor.Sensor, cg.PollingComponent) 39 | else: 40 | raise NotImplementedError 41 | 42 | 43 | def validate_timeout(n): 44 | n = cv.positive_time_period_microseconds(n) 45 | if n.total_seconds > 8: 46 | raise cv.Invalid("Maximum timeout cannot be greater than 8 seconds") 47 | return n 48 | 49 | 50 | CONF_LOSS = "loss" 51 | CONF_LATENCY = "latency" 52 | 53 | CONFIG_SCHEMA = cv.Schema( 54 | { 55 | cv.GenerateID(): cv.declare_id(PingSensor), 56 | cv.Optional(CONF_IP_ADDRESS, default="8.8.8.8"): cv.string, 57 | cv.Optional(CONF_TIMEOUT, default="1sec"): cv.positive_time_period_milliseconds, 58 | cv.Optional(CONF_NUM_ATTEMPTS, default=13): cv.int_range(min=1, max=60), 59 | cv.Optional(CONF_LOSS): sensor.sensor_schema( 60 | unit_of_measurement=UNIT_PERCENT, 61 | icon=ICON_EMPTY, 62 | accuracy_decimals=0, 63 | device_class=DEVICE_CLASS_EMPTY, 64 | state_class=STATE_CLASS_MEASUREMENT, 65 | ), 66 | cv.Optional(CONF_LATENCY): sensor.sensor_schema( 67 | unit_of_measurement=UNIT_SECOND, 68 | icon=ICON_EMPTY, 69 | accuracy_decimals=0, 70 | device_class=DEVICE_CLASS_EMPTY, 71 | state_class=STATE_CLASS_MEASUREMENT, 72 | ), 73 | } 74 | ).extend(cv.polling_component_schema("60s")) 75 | 76 | 77 | async def to_code(config): 78 | var = cg.new_Pvariable(config[CONF_ID]) 79 | await cg.register_component(var, config) 80 | 81 | cg.add(var.set_target(config[CONF_IP_ADDRESS])) 82 | cg.add(var.set_timeout(config[CONF_TIMEOUT])) 83 | cg.add(var.set_n_packet(config[CONF_NUM_ATTEMPTS])) 84 | 85 | if CONF_LOSS in config: 86 | sens = await sensor.new_sensor(config[CONF_LOSS]) 87 | cg.add(var.set_packet_loss_sensor(sens)) 88 | if CONF_LATENCY in config: 89 | sens = await sensor.new_sensor(config[CONF_LATENCY]) 90 | cg.add(var.set_latency_sensor(sens)) 91 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | # Gitignore settings for ESPHome 2 | # This is an example and may include too much for your use-case. 3 | # You can modify this file to suit your needs. 4 | /.esphome/ 5 | **/.pioenvs/ 6 | **/.piolibdeps/ 7 | **/lib/ 8 | **/src/ 9 | **/platformio.ini 10 | /secrets.yaml 11 | -------------------------------------------------------------------------------- /config/influxdb.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # An example to send results to influxdb. 3 | substitutions: 4 | my_name: influxdb-example 5 | my_location: somewhere 6 | influxdb_host: influxdb.example.org 7 | influxdb_port: "8086" 8 | influxdb_user: !secret influxdb_user 9 | influxdb_password: !secret influxdb_password 10 | influxdb_database: my_database 11 | target_ip_address: 8.8.4.4 12 | 13 | globals: 14 | - id: buf 15 | type: char[512] 16 | restore_value: no 17 | initial_value: "" 18 | 19 | esphome: 20 | name: ${my_name} 21 | platform: ESP8266 22 | board: nodemcuv2 23 | libraries: 24 | - ESP8266WiFi 25 | - https://github.com/akaJes/AsyncPing#95ac7e4 26 | logger: 27 | ota: 28 | password: !secret ota_password 29 | api: 30 | wifi: 31 | ssid: !secret wifi_ssid 32 | password: !secret wifi_password 33 | ap: 34 | ssid: ${my_name} 35 | password: !secret wifi_ap_password 36 | external_components: 37 | - source: 38 | type: local 39 | path: ../components 40 | 41 | http_request: 42 | useragent: esphome/${my_name} 43 | timeout: 10s 44 | 45 | sensor: 46 | - platform: ping 47 | ip_address: ${target_ip_address} 48 | num_attempts: 17 49 | timeout: 1sec 50 | loss: 51 | name: Packet loss 52 | id: loss 53 | on_value: 54 | then: 55 | - http_request.post: 56 | url: "http://${influxdb_host}:${influxdb_port}/write?db=${influxdb_database}&u=${influxdb_user}&p=${influxdb_password}" 57 | body: !lambda |- 58 | snprintf(id(buf), sizeof(id(buf)), "ping,location=${my_location},host=${my_name},url=${target_ip_address} percent_packet_loss=%0.1f", id(loss).state); 59 | return id(buf); 60 | latency: 61 | name: Latency 62 | accuracy_decimals: 3 63 | id: latency 64 | on_value: 65 | then: 66 | - http_request.post: 67 | url: "http://${influxdb_host}:${influxdb_port}/write?db=${influxdb_database}&u=${influxdb_user}&p=${influxdb_password}" 68 | body: !lambda |- 69 | snprintf(id(buf), sizeof(id(buf)), "ping,location=${my_location},host=${my_name},url=${target_ip_address} average_response_ms=%0.1f", id(latency).state * 1000); 70 | return id(buf); 71 | -------------------------------------------------------------------------------- /config/ping-esp32.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | my_name: ping-sensor 3 | 4 | esphome: 5 | name: ${my_name} 6 | platform: ESP32 7 | board: lolin32 8 | 9 | logger: 10 | 11 | web_server: 12 | 13 | ota: 14 | password: !secret ota_password 15 | 16 | wifi: 17 | ssid: !secret wifi_ssid 18 | password: !secret wifi_password 19 | ap: 20 | ssid: Ping 21 | password: !secret wifi_ap_password 22 | 23 | captive_portal: 24 | 25 | external_components: 26 | - source: 27 | type: local 28 | path: ../components 29 | 30 | sensor: 31 | - platform: ping 32 | ip_address: 8.8.8.8 33 | num_attempts: 17 34 | timeout: 1sec 35 | loss: 36 | name: Packet loss 37 | latency: 38 | name: Latency 39 | accuracy_decimals: 3 40 | -------------------------------------------------------------------------------- /config/ping-esp32c3.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | my_name: ping-sensor-esp32c3 3 | 4 | esphome: 5 | name: ${my_name} 6 | 7 | esp32: 8 | board: esp32-c3-devkitm-1 9 | framework: 10 | type: esp-idf 11 | variant: ESP32C3 12 | 13 | logger: 14 | 15 | ota: 16 | password: !secret ota_password 17 | 18 | wifi: 19 | ssid: !secret wifi_ssid 20 | password: !secret wifi_password 21 | ap: 22 | ssid: Ping 23 | password: !secret wifi_ap_password 24 | 25 | external_components: 26 | - source: 27 | type: local 28 | path: ../components 29 | 30 | sensor: 31 | - platform: ping 32 | ip_address: 8.8.8.8 33 | num_attempts: 17 34 | timeout: 1sec 35 | loss: 36 | name: Packet loss 37 | latency: 38 | name: Latency 39 | accuracy_decimals: 3 40 | -------------------------------------------------------------------------------- /config/ping.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | my_name: ping-sensor 3 | 4 | esphome: 5 | name: ${my_name} 6 | platform: ESP8266 7 | board: nodemcuv2 8 | libraries: 9 | - ESP8266WiFi 10 | - https://github.com/akaJes/AsyncPing#95ac7e4 11 | 12 | logger: 13 | 14 | web_server: 15 | 16 | ota: 17 | password: !secret ota_password 18 | 19 | wifi: 20 | ssid: !secret wifi_ssid 21 | password: !secret wifi_password 22 | ap: 23 | ssid: Ping 24 | password: !secret wifi_ap_password 25 | 26 | captive_portal: 27 | 28 | external_components: 29 | - source: 30 | type: local 31 | path: ../components 32 | 33 | sensor: 34 | - platform: ping 35 | 36 | # IP address of the target 37 | ip_address: 8.8.8.8 38 | 39 | # number of packets to send 40 | num_attempts: 17 41 | 42 | # the timeout. however, this is not what you usually expect from `ping` 43 | # implementation: the timeout is also the interval to send packets. if you 44 | # set this value to 10 sec, and the network is fine (no packet loss), then 45 | # the component sends a packet at 10 sec interval, and the total time to 46 | # finish would be 10 sec * num_attempts = 10 * 17 = 170 sec. 47 | timeout: 1sec 48 | 49 | loss: 50 | # the name to be shown. 51 | name: Packet loss 52 | 53 | latency: 54 | # the name to be shown. 55 | name: Latency 56 | # this should be 3 as the value is float, unit is sec, and the raw 57 | # values are in ms. 58 | accuracy_decimals: 3 59 | 60 | # the interval for checking the sensors. defaults to 60s. 61 | update_interval: 30s 62 | -------------------------------------------------------------------------------- /config/secrets.yaml.dist: -------------------------------------------------------------------------------- 1 | wifi_ssid: my_ssid 2 | wifi_password: my_password 3 | 4 | wifi_ap_password: password 5 | ota_password: password 6 | 7 | influxdb_user: user 8 | influxdb_password: password 9 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | reports=no 3 | ignore=api_pb2.py 4 | 5 | disable= 6 | format, 7 | missing-docstring, 8 | fixme, 9 | unused-argument, 10 | global-statement, 11 | too-few-public-methods, 12 | too-many-lines, 13 | too-many-locals, 14 | too-many-ancestors, 15 | too-many-branches, 16 | too-many-statements, 17 | too-many-arguments, 18 | too-many-return-statements, 19 | too-many-instance-attributes, 20 | duplicate-code, 21 | invalid-name, 22 | cyclic-import, 23 | redefined-builtin, 24 | undefined-loop-variable, 25 | useless-object-inheritance, 26 | stop-iteration-return, 27 | no-self-use, 28 | import-outside-toplevel, 29 | # Broken 30 | unsupported-membership-test, 31 | unsubscriptable-object, 32 | --------------------------------------------------------------------------------