updates
This commit is contained in:
		
							
								
								
									
										8
									
								
								deps/bin/eq3cli
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										8
									
								
								deps/bin/eq3cli
									
									
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
#!/usr/local/bin/python3
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
from eq3bt.eq3cli import cli
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
 | 
			
		||||
    sys.exit(cli())
 | 
			
		||||
							
								
								
									
										225
									
								
								deps/lib/python3.10/site-packages/CHANGELOG
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								deps/lib/python3.10/site-packages/CHANGELOG
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,225 @@
 | 
			
		||||
Changelog
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
0.2 (2022-07-13)
 | 
			
		||||
----------------
 | 
			
		||||
 | 
			
		||||
- Add publish to pypi workflow (#54) [Teemu R]
 | 
			
		||||
 | 
			
		||||
- Add bleak backend and make it default (#53) [Teemu R]
 | 
			
		||||
 | 
			
		||||
- Wrap backend exceptions inside BackendException (#52) [Teemu R]
 | 
			
		||||
 | 
			
		||||
- Add mac property to thermostat class (#51) [Teemu R]
 | 
			
		||||
 | 
			
		||||
- Update README, pyproject.toml (#49) [Teemu R]
 | 
			
		||||
 | 
			
		||||
- Support gattlib as an alternative btle library (#48) [Helmut Grohne]
 | 
			
		||||
 | 
			
		||||
- Use poetry, add pre-commit hooks & mass format to modern standards,
 | 
			
		||||
  add CI (#47) [Teemu R]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
0.1.12 (2021-11-13)
 | 
			
		||||
-------------------
 | 
			
		||||
 | 
			
		||||
- Add bt interface selection (#44) [Hummel95]
 | 
			
		||||
 | 
			
		||||
0.1.11 (2019-05-27)
 | 
			
		||||
-------------------
 | 
			
		||||
 | 
			
		||||
- Decoding presets in status messages (#33) [Matthias Erll]
 | 
			
		||||
 | 
			
		||||
- Adding device serial number and firmware (#31) [Matthias Erll]
 | 
			
		||||
 | 
			
		||||
- Context.invoke() -> Context.forward() (#28) [Till]
 | 
			
		||||
 | 
			
		||||
- Require python 3.4 or newer in setup.py, closes #23. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
0.1.10 (2018-11-09))
 | 
			
		||||
------------------------
 | 
			
		||||
- Context.invoke() -> Context.forward() (#28) [Till]
 | 
			
		||||
- Require python 3.4 or newer in setup.py, closes #23. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
0.1.9 (2018-02-18)
 | 
			
		||||
------------------------
 | 
			
		||||
 | 
			
		||||
- Update to the new construct API (#20) [Arkadiusz Bulski]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
0.1.8 (2018-01-20)
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
- Update to work with the newest construct release, bump version. [Teemu
 | 
			
		||||
  Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Update schedule example, fixes #15. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Do not suppress exceptions from bluepy, but log them to debug logger
 | 
			
		||||
  and raise exceptions for users to handle. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Install flake8 and pylint, which are required for the travis build.
 | 
			
		||||
  [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
0.1.7 (2017-10-06)
 | 
			
		||||
------------------------
 | 
			
		||||
- Fixed setting schedule not working (#9) [horsitis
 | 
			
		||||
 | 
			
		||||
0.1.6 (2017-04-01)
 | 
			
		||||
------------------------
 | 
			
		||||
 | 
			
		||||
- Version 0.1.6. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Use debug logging for the first round of connection error. [Teemu
 | 
			
		||||
  Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Disallow running with python versions older than 3.4. [Teemu
 | 
			
		||||
  Rytilahti]
 | 
			
		||||
 | 
			
		||||
  The library _may_ still be python2 compatible though for now,
 | 
			
		||||
  but this is unsupported and should not be relied on.
 | 
			
		||||
 | 
			
		||||
- On/Off/Manual mode fixes (#6) [Janne Grunau]
 | 
			
		||||
 | 
			
		||||
  * Handle On/Off mode correctly
 | 
			
		||||
 | 
			
		||||
  * Set temperature in [EQ3BT_MIN_TEMP, EQ3BT_MAX_TEMP] for manual mode
 | 
			
		||||
 | 
			
		||||
  * simplify mode setter function
 | 
			
		||||
 | 
			
		||||
- Be less noisy on connection errors. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Require and validate mac address at the cli (#4) [Klemens Schölhorn]
 | 
			
		||||
 | 
			
		||||
- Add missing structures.py. this was already in pypi package, so no
 | 
			
		||||
  harm done. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
0.1.5 (2017-01-28)
 | 
			
		||||
------------------------
 | 
			
		||||
 | 
			
		||||
- Version 0.1.5. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Fix manual on/off handling, cleanup the code for next release. [Teemu
 | 
			
		||||
  Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Make Thermostat testable. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Use less magic constants and more structures, fix manual mode setting.
 | 
			
		||||
  [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Fix setup.py typo. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Eq3cli: add away command. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Restructuring with construct for more readable code. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
  * add set_away(away_ends, temperature) for enabling and disabling away mode
 | 
			
		||||
 | 
			
		||||
- Add hound-ci config. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
0.1.4 (2017-01-15)
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
- Version 0.1.4. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Add away_end property. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Add changelog. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
0.1.3 (2017-01-15)
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
- Make eq3bt a module. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Update README. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Add scheduling and offset functionality. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Connection: pretty print messaging in hex. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Setup.py: fix console script location. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
0.1.2 (2017-01-14)
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
- Fix packaging, add click dependency, bump to 0.1.2. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Bump bluepy requirement to 1.0.5. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
0.1 (2017-01-14)
 | 
			
		||||
----------------
 | 
			
		||||
 | 
			
		||||
- Restructure bluepy_devices to python-eq3bt. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
  * Complete restructure of the library. All unnecessary and problematic parts are dropped.
 | 
			
		||||
  * General cleaning up, making flake8 and pylint happy.
 | 
			
		||||
  * Updated and documented cli tool, named eq3cli
 | 
			
		||||
 | 
			
		||||
- Add contextmanager for connection to simplify connecting and
 | 
			
		||||
  disconnecting. Calling writes on device will build and tear down the
 | 
			
		||||
  connection automatically. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Eq3btsmart: do not try to connect on init, allows adding the component
 | 
			
		||||
  to homeassistant even if the device is not connectable at the moment.
 | 
			
		||||
  [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Add eq3cli tool. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
  Included command-line tool can be used to control the device.
 | 
			
		||||
  All current functionality is available through it, check updated README.md for usage.
 | 
			
		||||
 | 
			
		||||
- Add logger to ease debugging. [Teemu Rytilahti]
 | 
			
		||||
 | 
			
		||||
- Increase version to 0.3.0 for the enhaced eq3btsmart support. [Janne
 | 
			
		||||
  Grunau]
 | 
			
		||||
 | 
			
		||||
- Eq3btsmart: and support for the comfort and eco temperature presets.
 | 
			
		||||
  [Janne Grunau]
 | 
			
		||||
 | 
			
		||||
- Eq3btsmart: add a property for the low battery warning. [Janne Grunau]
 | 
			
		||||
 | 
			
		||||
- Eq3btsmart: add support for the thermostat's operating lock. [Janne
 | 
			
		||||
  Grunau]
 | 
			
		||||
 | 
			
		||||
- Eq3btsmart: add window open mode configuration. [Janne Grunau]
 | 
			
		||||
 | 
			
		||||
- Eq3btsmart: and property to check window open state. [Janne Grunau]
 | 
			
		||||
 | 
			
		||||
- Eq3btsmart: report valve state. [Janne Grunau]
 | 
			
		||||
 | 
			
		||||
- Eq3btsmart: control the supported modes of the thermostat. [Janne
 | 
			
		||||
  Grunau]
 | 
			
		||||
 | 
			
		||||
  The away mode is not really useful yet.
 | 
			
		||||
 | 
			
		||||
- Eq3btsmart: verify that temperatures are in min/max range. [Janne
 | 
			
		||||
  Grunau]
 | 
			
		||||
 | 
			
		||||
- Eq3btsmart: fix the minimal and maximal temperatures. [Janne Grunau]
 | 
			
		||||
 | 
			
		||||
  4.5 and 30 degree celsius have special meanings and can't be set
 | 
			
		||||
  in 'auto' mode. 4.5 means off (valve closed even if the temperature
 | 
			
		||||
  below 4.5 degress). 30 means on (valve permanently open even if the
 | 
			
		||||
  temperature exceeds 30 degrees).
 | 
			
		||||
 | 
			
		||||
- Eq3btsmart: the update request needs to include the full time. [Janne
 | 
			
		||||
  Grunau]
 | 
			
		||||
 | 
			
		||||
  Otherwise the thermostat can set a random time. Also fixes the format of
 | 
			
		||||
  the set time request.
 | 
			
		||||
 | 
			
		||||
- Initial update in eq3btsmart.py. [Markus Peter]
 | 
			
		||||
 | 
			
		||||
- +travis. [Markus Peter]
 | 
			
		||||
 | 
			
		||||
- Update README.md. [Markus Peter]
 | 
			
		||||
 | 
			
		||||
- Create README.md. [Markus Peter]
 | 
			
		||||
 | 
			
		||||
- Initial Commit Version 0.2.0. [Markus Peter]
 | 
			
		||||
 | 
			
		||||
- Initial commit. [Markus Peter]
 | 
			
		||||
							
								
								
									
										1
									
								
								deps/lib/python3.10/site-packages/aiofiles-0.8.0.dist-info/INSTALLER
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								deps/lib/python3.10/site-packages/aiofiles-0.8.0.dist-info/INSTALLER
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
pip
 | 
			
		||||
							
								
								
									
										202
									
								
								deps/lib/python3.10/site-packages/aiofiles-0.8.0.dist-info/LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								deps/lib/python3.10/site-packages/aiofiles-0.8.0.dist-info/LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,202 @@
 | 
			
		||||
Apache License
 | 
			
		||||
                           Version 2.0, January 2004
 | 
			
		||||
                        http://www.apache.org/licenses/
 | 
			
		||||
 | 
			
		||||
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 | 
			
		||||
 | 
			
		||||
   1. Definitions.
 | 
			
		||||
 | 
			
		||||
      "License" shall mean the terms and conditions for use, reproduction,
 | 
			
		||||
      and distribution as defined by Sections 1 through 9 of this document.
 | 
			
		||||
 | 
			
		||||
      "Licensor" shall mean the copyright owner or entity authorized by
 | 
			
		||||
      the copyright owner that is granting the License.
 | 
			
		||||
 | 
			
		||||
      "Legal Entity" shall mean the union of the acting entity and all
 | 
			
		||||
      other entities that control, are controlled by, or are under common
 | 
			
		||||
      control with that entity. For the purposes of this definition,
 | 
			
		||||
      "control" means (i) the power, direct or indirect, to cause the
 | 
			
		||||
      direction or management of such entity, whether by contract or
 | 
			
		||||
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
 | 
			
		||||
      outstanding shares, or (iii) beneficial ownership of such entity.
 | 
			
		||||
 | 
			
		||||
      "You" (or "Your") shall mean an individual or Legal Entity
 | 
			
		||||
      exercising permissions granted by this License.
 | 
			
		||||
 | 
			
		||||
      "Source" form shall mean the preferred form for making modifications,
 | 
			
		||||
      including but not limited to software source code, documentation
 | 
			
		||||
      source, and configuration files.
 | 
			
		||||
 | 
			
		||||
      "Object" form shall mean any form resulting from mechanical
 | 
			
		||||
      transformation or translation of a Source form, including but
 | 
			
		||||
      not limited to compiled object code, generated documentation,
 | 
			
		||||
      and conversions to other media types.
 | 
			
		||||
 | 
			
		||||
      "Work" shall mean the work of authorship, whether in Source or
 | 
			
		||||
      Object form, made available under the License, as indicated by a
 | 
			
		||||
      copyright notice that is included in or attached to the work
 | 
			
		||||
      (an example is provided in the Appendix below).
 | 
			
		||||
 | 
			
		||||
      "Derivative Works" shall mean any work, whether in Source or Object
 | 
			
		||||
      form, that is based on (or derived from) the Work and for which the
 | 
			
		||||
      editorial revisions, annotations, elaborations, or other modifications
 | 
			
		||||
      represent, as a whole, an original work of authorship. For the purposes
 | 
			
		||||
      of this License, Derivative Works shall not include works that remain
 | 
			
		||||
      separable from, or merely link (or bind by name) to the interfaces of,
 | 
			
		||||
      the Work and Derivative Works thereof.
 | 
			
		||||
 | 
			
		||||
      "Contribution" shall mean any work of authorship, including
 | 
			
		||||
      the original version of the Work and any modifications or additions
 | 
			
		||||
      to that Work or Derivative Works thereof, that is intentionally
 | 
			
		||||
      submitted to Licensor for inclusion in the Work by the copyright owner
 | 
			
		||||
      or by an individual or Legal Entity authorized to submit on behalf of
 | 
			
		||||
      the copyright owner. For the purposes of this definition, "submitted"
 | 
			
		||||
      means any form of electronic, verbal, or written communication sent
 | 
			
		||||
      to the Licensor or its representatives, including but not limited to
 | 
			
		||||
      communication on electronic mailing lists, source code control systems,
 | 
			
		||||
      and issue tracking systems that are managed by, or on behalf of, the
 | 
			
		||||
      Licensor for the purpose of discussing and improving the Work, but
 | 
			
		||||
      excluding communication that is conspicuously marked or otherwise
 | 
			
		||||
      designated in writing by the copyright owner as "Not a Contribution."
 | 
			
		||||
 | 
			
		||||
      "Contributor" shall mean Licensor and any individual or Legal Entity
 | 
			
		||||
      on behalf of whom a Contribution has been received by Licensor and
 | 
			
		||||
      subsequently incorporated within the Work.
 | 
			
		||||
 | 
			
		||||
   2. Grant of Copyright License. Subject to the terms and conditions of
 | 
			
		||||
      this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
      copyright license to reproduce, prepare Derivative Works of,
 | 
			
		||||
      publicly display, publicly perform, sublicense, and distribute the
 | 
			
		||||
      Work and such Derivative Works in Source or Object form.
 | 
			
		||||
 | 
			
		||||
   3. Grant of Patent License. Subject to the terms and conditions of
 | 
			
		||||
      this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
      (except as stated in this section) patent license to make, have made,
 | 
			
		||||
      use, offer to sell, sell, import, and otherwise transfer the Work,
 | 
			
		||||
      where such license applies only to those patent claims licensable
 | 
			
		||||
      by such Contributor that are necessarily infringed by their
 | 
			
		||||
      Contribution(s) alone or by combination of their Contribution(s)
 | 
			
		||||
      with the Work to which such Contribution(s) was submitted. If You
 | 
			
		||||
      institute patent litigation against any entity (including a
 | 
			
		||||
      cross-claim or counterclaim in a lawsuit) alleging that the Work
 | 
			
		||||
      or a Contribution incorporated within the Work constitutes direct
 | 
			
		||||
      or contributory patent infringement, then any patent licenses
 | 
			
		||||
      granted to You under this License for that Work shall terminate
 | 
			
		||||
      as of the date such litigation is filed.
 | 
			
		||||
 | 
			
		||||
   4. Redistribution. You may reproduce and distribute copies of the
 | 
			
		||||
      Work or Derivative Works thereof in any medium, with or without
 | 
			
		||||
      modifications, and in Source or Object form, provided that You
 | 
			
		||||
      meet the following conditions:
 | 
			
		||||
 | 
			
		||||
      (a) You must give any other recipients of the Work or
 | 
			
		||||
          Derivative Works a copy of this License; and
 | 
			
		||||
 | 
			
		||||
      (b) You must cause any modified files to carry prominent notices
 | 
			
		||||
          stating that You changed the files; and
 | 
			
		||||
 | 
			
		||||
      (c) You must retain, in the Source form of any Derivative Works
 | 
			
		||||
          that You distribute, all copyright, patent, trademark, and
 | 
			
		||||
          attribution notices from the Source form of the Work,
 | 
			
		||||
          excluding those notices that do not pertain to any part of
 | 
			
		||||
          the Derivative Works; and
 | 
			
		||||
 | 
			
		||||
      (d) If the Work includes a "NOTICE" text file as part of its
 | 
			
		||||
          distribution, then any Derivative Works that You distribute must
 | 
			
		||||
          include a readable copy of the attribution notices contained
 | 
			
		||||
          within such NOTICE file, excluding those notices that do not
 | 
			
		||||
          pertain to any part of the Derivative Works, in at least one
 | 
			
		||||
          of the following places: within a NOTICE text file distributed
 | 
			
		||||
          as part of the Derivative Works; within the Source form or
 | 
			
		||||
          documentation, if provided along with the Derivative Works; or,
 | 
			
		||||
          within a display generated by the Derivative Works, if and
 | 
			
		||||
          wherever such third-party notices normally appear. The contents
 | 
			
		||||
          of the NOTICE file are for informational purposes only and
 | 
			
		||||
          do not modify the License. You may add Your own attribution
 | 
			
		||||
          notices within Derivative Works that You distribute, alongside
 | 
			
		||||
          or as an addendum to the NOTICE text from the Work, provided
 | 
			
		||||
          that such additional attribution notices cannot be construed
 | 
			
		||||
          as modifying the License.
 | 
			
		||||
 | 
			
		||||
      You may add Your own copyright statement to Your modifications and
 | 
			
		||||
      may provide additional or different license terms and conditions
 | 
			
		||||
      for use, reproduction, or distribution of Your modifications, or
 | 
			
		||||
      for any such Derivative Works as a whole, provided Your use,
 | 
			
		||||
      reproduction, and distribution of the Work otherwise complies with
 | 
			
		||||
      the conditions stated in this License.
 | 
			
		||||
 | 
			
		||||
   5. Submission of Contributions. Unless You explicitly state otherwise,
 | 
			
		||||
      any Contribution intentionally submitted for inclusion in the Work
 | 
			
		||||
      by You to the Licensor shall be under the terms and conditions of
 | 
			
		||||
      this License, without any additional terms or conditions.
 | 
			
		||||
      Notwithstanding the above, nothing herein shall supersede or modify
 | 
			
		||||
      the terms of any separate license agreement you may have executed
 | 
			
		||||
      with Licensor regarding such Contributions.
 | 
			
		||||
 | 
			
		||||
   6. Trademarks. This License does not grant permission to use the trade
 | 
			
		||||
      names, trademarks, service marks, or product names of the Licensor,
 | 
			
		||||
      except as required for reasonable and customary use in describing the
 | 
			
		||||
      origin of the Work and reproducing the content of the NOTICE file.
 | 
			
		||||
 | 
			
		||||
   7. Disclaimer of Warranty. Unless required by applicable law or
 | 
			
		||||
      agreed to in writing, Licensor provides the Work (and each
 | 
			
		||||
      Contributor provides its Contributions) on an "AS IS" BASIS,
 | 
			
		||||
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
      implied, including, without limitation, any warranties or conditions
 | 
			
		||||
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 | 
			
		||||
      PARTICULAR PURPOSE. You are solely responsible for determining the
 | 
			
		||||
      appropriateness of using or redistributing the Work and assume any
 | 
			
		||||
      risks associated with Your exercise of permissions under this License.
 | 
			
		||||
 | 
			
		||||
   8. Limitation of Liability. In no event and under no legal theory,
 | 
			
		||||
      whether in tort (including negligence), contract, or otherwise,
 | 
			
		||||
      unless required by applicable law (such as deliberate and grossly
 | 
			
		||||
      negligent acts) or agreed to in writing, shall any Contributor be
 | 
			
		||||
      liable to You for damages, including any direct, indirect, special,
 | 
			
		||||
      incidental, or consequential damages of any character arising as a
 | 
			
		||||
      result of this License or out of the use or inability to use the
 | 
			
		||||
      Work (including but not limited to damages for loss of goodwill,
 | 
			
		||||
      work stoppage, computer failure or malfunction, or any and all
 | 
			
		||||
      other commercial damages or losses), even if such Contributor
 | 
			
		||||
      has been advised of the possibility of such damages.
 | 
			
		||||
 | 
			
		||||
   9. Accepting Warranty or Additional Liability. While redistributing
 | 
			
		||||
      the Work or Derivative Works thereof, You may choose to offer,
 | 
			
		||||
      and charge a fee for, acceptance of support, warranty, indemnity,
 | 
			
		||||
      or other liability obligations and/or rights consistent with this
 | 
			
		||||
      License. However, in accepting such obligations, You may act only
 | 
			
		||||
      on Your own behalf and on Your sole responsibility, not on behalf
 | 
			
		||||
      of any other Contributor, and only if You agree to indemnify,
 | 
			
		||||
      defend, and hold each Contributor harmless for any liability
 | 
			
		||||
      incurred by, or claims asserted against, such Contributor by reason
 | 
			
		||||
      of your accepting any such warranty or additional liability.
 | 
			
		||||
 | 
			
		||||
   END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
   APPENDIX: How to apply the Apache License to your work.
 | 
			
		||||
 | 
			
		||||
      To apply the Apache License to your work, attach the following
 | 
			
		||||
      boilerplate notice, with the fields enclosed by brackets "{}"
 | 
			
		||||
      replaced with your own identifying information. (Don't include
 | 
			
		||||
      the brackets!)  The text should be enclosed in the appropriate
 | 
			
		||||
      comment syntax for the file format. We also recommend that a
 | 
			
		||||
      file or class name and description of purpose be included on the
 | 
			
		||||
      same "printed page" as the copyright notice for easier
 | 
			
		||||
      identification within third-party archives.
 | 
			
		||||
 | 
			
		||||
   Copyright {yyyy} {name of copyright owner}
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										239
									
								
								deps/lib/python3.10/site-packages/aiofiles-0.8.0.dist-info/METADATA
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								deps/lib/python3.10/site-packages/aiofiles-0.8.0.dist-info/METADATA
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,239 @@
 | 
			
		||||
Metadata-Version: 2.1
 | 
			
		||||
Name: aiofiles
 | 
			
		||||
Version: 0.8.0
 | 
			
		||||
Summary: File support for asyncio.
 | 
			
		||||
License: Apache-2.0
 | 
			
		||||
Author: Tin Tvrtkovic
 | 
			
		||||
Author-email: tinchester@gmail.com
 | 
			
		||||
Requires-Python: >=3.6,<4.0
 | 
			
		||||
Classifier: License :: OSI Approved :: Apache Software License
 | 
			
		||||
Classifier: Programming Language :: Python :: 3
 | 
			
		||||
Classifier: Programming Language :: Python :: 3.6
 | 
			
		||||
Classifier: Programming Language :: Python :: 3.7
 | 
			
		||||
Classifier: Programming Language :: Python :: 3.8
 | 
			
		||||
Classifier: Programming Language :: Python :: 3.9
 | 
			
		||||
Classifier: Programming Language :: Python :: 3.10
 | 
			
		||||
Description-Content-Type: text/x-rst
 | 
			
		||||
 | 
			
		||||
aiofiles: file support for asyncio
 | 
			
		||||
==================================
 | 
			
		||||
 | 
			
		||||
.. image:: https://img.shields.io/pypi/v/aiofiles.svg
 | 
			
		||||
        :target: https://pypi.python.org/pypi/aiofiles
 | 
			
		||||
 | 
			
		||||
.. image:: https://travis-ci.org/Tinche/aiofiles.svg?branch=master
 | 
			
		||||
        :target: https://travis-ci.org/Tinche/aiofiles
 | 
			
		||||
 | 
			
		||||
.. image:: https://codecov.io/gh/Tinche/aiofiles/branch/master/graph/badge.svg
 | 
			
		||||
        :target: https://codecov.io/gh/Tinche/aiofiles
 | 
			
		||||
 | 
			
		||||
.. image:: https://img.shields.io/pypi/pyversions/aiofiles.svg
 | 
			
		||||
        :target: https://github.com/Tinche/aiofiles
 | 
			
		||||
        :alt: Supported Python versions
 | 
			
		||||
 | 
			
		||||
**aiofiles** is an Apache2 licensed library, written in Python, for handling local
 | 
			
		||||
disk files in asyncio applications.
 | 
			
		||||
 | 
			
		||||
Ordinary local file IO is blocking, and cannot easily and portably made
 | 
			
		||||
asynchronous. This means doing file IO may interfere with asyncio applications,
 | 
			
		||||
which shouldn't block the executing thread. aiofiles helps with this by
 | 
			
		||||
introducing asynchronous versions of files that support delegating operations to
 | 
			
		||||
a separate thread pool.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    async with aiofiles.open('filename', mode='r') as f:
 | 
			
		||||
        contents = await f.read()
 | 
			
		||||
    print(contents)
 | 
			
		||||
    'My file contents'
 | 
			
		||||
 | 
			
		||||
Asynchronous iteration is also supported.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    async with aiofiles.open('filename') as f:
 | 
			
		||||
        async for line in f:
 | 
			
		||||
            ...
 | 
			
		||||
 | 
			
		||||
Asynchronous interface to tempfile module.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    async with aiofiles.tempfile.TemporaryFile('wb') as f:
 | 
			
		||||
        await f.write(b'Hello, World!')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Features
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
- a file API very similar to Python's standard, blocking API
 | 
			
		||||
- support for buffered and unbuffered binary files, and buffered text files
 | 
			
		||||
- support for ``async``/``await`` (:PEP:`492`) constructs
 | 
			
		||||
- async interface to tempfile module
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Installation
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
To install aiofiles, simply:
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
 | 
			
		||||
    $ pip install aiofiles
 | 
			
		||||
 | 
			
		||||
Usage
 | 
			
		||||
-----
 | 
			
		||||
 | 
			
		||||
Files are opened using the ``aiofiles.open()`` coroutine, which in addition to
 | 
			
		||||
mirroring the builtin ``open`` accepts optional ``loop`` and ``executor``
 | 
			
		||||
arguments. If ``loop`` is absent, the default loop will be used, as per the
 | 
			
		||||
set asyncio policy. If ``executor`` is not specified, the default event loop
 | 
			
		||||
executor will be used.
 | 
			
		||||
 | 
			
		||||
In case of success, an asynchronous file object is returned with an
 | 
			
		||||
API identical to an ordinary file, except the following methods are coroutines
 | 
			
		||||
and delegate to an executor:
 | 
			
		||||
 | 
			
		||||
* ``close``
 | 
			
		||||
* ``flush``
 | 
			
		||||
* ``isatty``
 | 
			
		||||
* ``read``
 | 
			
		||||
* ``readall``
 | 
			
		||||
* ``read1``
 | 
			
		||||
* ``readinto``
 | 
			
		||||
* ``readline``
 | 
			
		||||
* ``readlines``
 | 
			
		||||
* ``seek``
 | 
			
		||||
* ``seekable``
 | 
			
		||||
* ``tell``
 | 
			
		||||
* ``truncate``
 | 
			
		||||
* ``writable``
 | 
			
		||||
* ``write``
 | 
			
		||||
* ``writelines``
 | 
			
		||||
 | 
			
		||||
In case of failure, one of the usual exceptions will be raised.
 | 
			
		||||
 | 
			
		||||
The ``aiofiles.os`` module contains executor-enabled coroutine versions of
 | 
			
		||||
several useful ``os`` functions that deal with files:
 | 
			
		||||
 | 
			
		||||
* ``stat``
 | 
			
		||||
* ``sendfile``
 | 
			
		||||
* ``rename``
 | 
			
		||||
* ``replace``
 | 
			
		||||
* ``remove``
 | 
			
		||||
* ``mkdir``
 | 
			
		||||
* ``makedirs``
 | 
			
		||||
* ``rmdir``
 | 
			
		||||
* ``removedirs``
 | 
			
		||||
* ``path.exists``
 | 
			
		||||
* ``path.isfile``
 | 
			
		||||
* ``path.isdir``
 | 
			
		||||
* ``path.getsize``
 | 
			
		||||
* ``path.getatime``
 | 
			
		||||
* ``path.getctime``
 | 
			
		||||
* ``path.samefile``
 | 
			
		||||
* ``path.sameopenfile``
 | 
			
		||||
 | 
			
		||||
Tempfile
 | 
			
		||||
~~~~~~~~
 | 
			
		||||
 | 
			
		||||
**aiofiles.tempfile** implements the following interfaces:
 | 
			
		||||
 | 
			
		||||
- TemporaryFile
 | 
			
		||||
- NamedTemporaryFile
 | 
			
		||||
- SpooledTemporaryFile
 | 
			
		||||
- TemporaryDirectory
 | 
			
		||||
 | 
			
		||||
Results return wrapped with a context manager allowing use with async with and async for.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    async with aiofiles.tempfile.NamedTemporaryFile('wb+') as f:
 | 
			
		||||
        await f.write(b'Line1\n Line2')
 | 
			
		||||
        await f.seek(0)
 | 
			
		||||
        async for line in f:
 | 
			
		||||
            print(line)
 | 
			
		||||
 | 
			
		||||
    async with aiofiles.tempfile.TemporaryDirectory() as d:
 | 
			
		||||
        filename = os.path.join(d, "file.ext")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Writing tests for aiofiles
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
Real file IO can be mocked by patching ``aiofiles.threadpool.sync_open``
 | 
			
		||||
as desired. The return type also needs to be registered with the
 | 
			
		||||
``aiofiles.threadpool.wrap`` dispatcher:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    aiofiles.threadpool.wrap.register(mock.MagicMock)(
 | 
			
		||||
        lambda *args, **kwargs: threadpool.AsyncBufferedIOBase(*args, **kwargs))
 | 
			
		||||
 | 
			
		||||
    async def test_stuff():
 | 
			
		||||
        data = 'data'
 | 
			
		||||
        mock_file = mock.MagicMock()
 | 
			
		||||
 | 
			
		||||
        with mock.patch('aiofiles.threadpool.sync_open', return_value=mock_file) as mock_open:
 | 
			
		||||
            async with aiofiles.open('filename', 'w') as f:
 | 
			
		||||
                await f.write(data)
 | 
			
		||||
 | 
			
		||||
            mock_file.write.assert_called_once_with(data)
 | 
			
		||||
 | 
			
		||||
History
 | 
			
		||||
~~~~~~~
 | 
			
		||||
0.8.0 (2021-11-27)
 | 
			
		||||
``````````````````
 | 
			
		||||
* aiofiles is now tested on Python 3.10.
 | 
			
		||||
* Added ``aiofiles.os.replace``.
 | 
			
		||||
  `#107 <https://github.com/Tinche/aiofiles/pull/107>`_
 | 
			
		||||
* Added ``aiofiles.os.{makedirs, removedirs}``.
 | 
			
		||||
* Added ``aiofiles.os.path.{exists, isfile, isdir, getsize, getatime, getctime, samefile, sameopenfile}``.
 | 
			
		||||
  `#63 <https://github.com/Tinche/aiofiles/pull/63>`_
 | 
			
		||||
* Added `suffix`, `prefix`, `dir` args to ``aiofiles.tempfile.TemporaryDirectory``.
 | 
			
		||||
  `#116 <https://github.com/Tinche/aiofiles/pull/116>`_
 | 
			
		||||
 | 
			
		||||
0.7.0 (2021-05-17)
 | 
			
		||||
``````````````````
 | 
			
		||||
- Added the ``aiofiles.tempfile`` module for async temporary files.
 | 
			
		||||
  `#56 <https://github.com/Tinche/aiofiles/pull/56>`_
 | 
			
		||||
- Switched to Poetry and GitHub actions.
 | 
			
		||||
- Dropped 3.5 support.
 | 
			
		||||
 | 
			
		||||
0.6.0 (2020-10-27)
 | 
			
		||||
``````````````````
 | 
			
		||||
- `aiofiles` is now tested on ppc64le.
 | 
			
		||||
- Added `name` and `mode` properties to async file objects.
 | 
			
		||||
  `#82 <https://github.com/Tinche/aiofiles/pull/82>`_
 | 
			
		||||
- Fixed a DeprecationWarning internally.
 | 
			
		||||
  `#75 <https://github.com/Tinche/aiofiles/pull/75>`_
 | 
			
		||||
- Python 3.9 support and tests.
 | 
			
		||||
 | 
			
		||||
0.5.0 (2020-04-12)
 | 
			
		||||
``````````````````
 | 
			
		||||
- Python 3.8 support. Code base modernization (using ``async/await`` instead of ``asyncio.coroutine``/``yield from``).
 | 
			
		||||
- Added ``aiofiles.os.remove``, ``aiofiles.os.rename``, ``aiofiles.os.mkdir``, ``aiofiles.os.rmdir``.
 | 
			
		||||
  `#62 <https://github.com/Tinche/aiofiles/pull/62>`_
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
0.4.0 (2018-08-11)
 | 
			
		||||
``````````````````
 | 
			
		||||
- Python 3.7 support.
 | 
			
		||||
- Removed Python 3.3/3.4 support. If you use these versions, stick to aiofiles 0.3.x.
 | 
			
		||||
 | 
			
		||||
0.3.2 (2017-09-23)
 | 
			
		||||
``````````````````
 | 
			
		||||
- The LICENSE is now included in the sdist.
 | 
			
		||||
  `#31 <https://github.com/Tinche/aiofiles/pull/31>`_
 | 
			
		||||
 | 
			
		||||
0.3.1 (2017-03-10)
 | 
			
		||||
``````````````````
 | 
			
		||||
 | 
			
		||||
- Introduced a changelog.
 | 
			
		||||
- ``aiofiles.os.sendfile`` will now work if the standard ``os`` module contains a ``sendfile`` function.
 | 
			
		||||
 | 
			
		||||
Contributing
 | 
			
		||||
~~~~~~~~~~~~
 | 
			
		||||
Contributions are very welcome. Tests can be run with ``tox``, please ensure
 | 
			
		||||
the coverage at least stays the same before you submit a pull request.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										26
									
								
								deps/lib/python3.10/site-packages/aiofiles-0.8.0.dist-info/RECORD
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								deps/lib/python3.10/site-packages/aiofiles-0.8.0.dist-info/RECORD
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
aiofiles-0.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
 | 
			
		||||
aiofiles-0.8.0.dist-info/LICENSE,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325
 | 
			
		||||
aiofiles-0.8.0.dist-info/METADATA,sha256=9WSIVaJhAbMi93mj4ir3932H5Je3JxAcUvH5hgyD0qU,7022
 | 
			
		||||
aiofiles-0.8.0.dist-info/RECORD,,
 | 
			
		||||
aiofiles-0.8.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
 | 
			
		||||
aiofiles-0.8.0.dist-info/WHEEL,sha256=FMw6u7Kp3jrdDDtJRZ3hkPV-9adLQT8pbM4iifKUGfw,85
 | 
			
		||||
aiofiles/__init__.py,sha256=6fPaAw6PXV8rszheTedkYcoLHC3KbbPH--eMODJ_-IE,136
 | 
			
		||||
aiofiles/__pycache__/__init__.cpython-310.pyc,,
 | 
			
		||||
aiofiles/__pycache__/base.cpython-310.pyc,,
 | 
			
		||||
aiofiles/__pycache__/os.cpython-310.pyc,,
 | 
			
		||||
aiofiles/__pycache__/ospath.cpython-310.pyc,,
 | 
			
		||||
aiofiles/base.py,sha256=_ntjFm4olO13y9JEQchLudmgPJ4mAFug5MRi6Efk7g0,2114
 | 
			
		||||
aiofiles/os.py,sha256=A8vZ0paqH14JaepvEdfSCv6BAFoV4D1LVmnKfkEG5tQ,719
 | 
			
		||||
aiofiles/ospath.py,sha256=cIyDMoGVHTqKoKFXfXVzuIROJQQRmeNqVuNuc9QgPY0,387
 | 
			
		||||
aiofiles/tempfile/__init__.py,sha256=y6siScSCxNkydsoAN0OlxSwvgqpk1XoVTvJ0gTvAmTk,7234
 | 
			
		||||
aiofiles/tempfile/__pycache__/__init__.cpython-310.pyc,,
 | 
			
		||||
aiofiles/tempfile/__pycache__/temptypes.cpython-310.pyc,,
 | 
			
		||||
aiofiles/tempfile/temptypes.py,sha256=ZwWLAV3eiWWW0QoRDPHN2U7XPf0DUQFYPRid7o4Sk9s,2169
 | 
			
		||||
aiofiles/threadpool/__init__.py,sha256=JaRiXZRaVvx32RvrUXwgZxAByrzqdmDcxi6t_P2iP6w,2273
 | 
			
		||||
aiofiles/threadpool/__pycache__/__init__.cpython-310.pyc,,
 | 
			
		||||
aiofiles/threadpool/__pycache__/binary.cpython-310.pyc,,
 | 
			
		||||
aiofiles/threadpool/__pycache__/text.cpython-310.pyc,,
 | 
			
		||||
aiofiles/threadpool/__pycache__/utils.cpython-310.pyc,,
 | 
			
		||||
aiofiles/threadpool/binary.py,sha256=tRdJnH6ragF5Kr13oIBPJrljgTl3hWSOaHSXfHESRBk,1167
 | 
			
		||||
aiofiles/threadpool/text.py,sha256=dNweKCxpwRgfqA6XCiWYDDddOTa0lbzH-Fh5o3rho-8,665
 | 
			
		||||
aiofiles/threadpool/utils.py,sha256=fcqvRBrcIk2qP-rOdm92zDHyCfgD6itS621V6oOLJwk,1912
 | 
			
		||||
							
								
								
									
										0
									
								
								deps/lib/python3.10/site-packages/aiofiles-0.8.0.dist-info/REQUESTED
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								deps/lib/python3.10/site-packages/aiofiles-0.8.0.dist-info/REQUESTED
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
								
								
									
										4
									
								
								deps/lib/python3.10/site-packages/aiofiles-0.8.0.dist-info/WHEEL
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								deps/lib/python3.10/site-packages/aiofiles-0.8.0.dist-info/WHEEL
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
Wheel-Version: 1.0
 | 
			
		||||
Generator: poetry 1.1.0a6
 | 
			
		||||
Root-Is-Purelib: true
 | 
			
		||||
Tag: py3-none-any
 | 
			
		||||
							
								
								
									
										5
									
								
								deps/lib/python3.10/site-packages/aiofiles/__init__.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								deps/lib/python3.10/site-packages/aiofiles/__init__.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
"""Utilities for asyncio-friendly file handling."""
 | 
			
		||||
from .threadpool import open
 | 
			
		||||
from . import tempfile
 | 
			
		||||
 | 
			
		||||
__all__ = ["open", "tempfile"]
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/__pycache__/__init__.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/__pycache__/__init__.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/__pycache__/base.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/__pycache__/base.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/__pycache__/os.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/__pycache__/os.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/__pycache__/ospath.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/__pycache__/ospath.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										91
									
								
								deps/lib/python3.10/site-packages/aiofiles/base.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								deps/lib/python3.10/site-packages/aiofiles/base.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
"""Various base classes."""
 | 
			
		||||
from types import coroutine
 | 
			
		||||
from collections.abc import Coroutine
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AsyncBase:
 | 
			
		||||
    def __init__(self, file, loop, executor):
 | 
			
		||||
        self._file = file
 | 
			
		||||
        self._loop = loop
 | 
			
		||||
        self._executor = executor
 | 
			
		||||
 | 
			
		||||
    def __aiter__(self):
 | 
			
		||||
        """We are our own iterator."""
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return super().__repr__() + " wrapping " + repr(self._file)
 | 
			
		||||
 | 
			
		||||
    async def __anext__(self):
 | 
			
		||||
        """Simulate normal file iteration."""
 | 
			
		||||
        line = await self.readline()
 | 
			
		||||
        if line:
 | 
			
		||||
            return line
 | 
			
		||||
        else:
 | 
			
		||||
            raise StopAsyncIteration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _ContextManager(Coroutine):
 | 
			
		||||
    __slots__ = ("_coro", "_obj")
 | 
			
		||||
 | 
			
		||||
    def __init__(self, coro):
 | 
			
		||||
        self._coro = coro
 | 
			
		||||
        self._obj = None
 | 
			
		||||
 | 
			
		||||
    def send(self, value):
 | 
			
		||||
        return self._coro.send(value)
 | 
			
		||||
 | 
			
		||||
    def throw(self, typ, val=None, tb=None):
 | 
			
		||||
        if val is None:
 | 
			
		||||
            return self._coro.throw(typ)
 | 
			
		||||
        elif tb is None:
 | 
			
		||||
            return self._coro.throw(typ, val)
 | 
			
		||||
        else:
 | 
			
		||||
            return self._coro.throw(typ, val, tb)
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        return self._coro.close()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def gi_frame(self):
 | 
			
		||||
        return self._coro.gi_frame
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def gi_running(self):
 | 
			
		||||
        return self._coro.gi_running
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def gi_code(self):
 | 
			
		||||
        return self._coro.gi_code
 | 
			
		||||
 | 
			
		||||
    def __next__(self):
 | 
			
		||||
        return self.send(None)
 | 
			
		||||
 | 
			
		||||
    @coroutine
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        resp = yield from self._coro
 | 
			
		||||
        return resp
 | 
			
		||||
 | 
			
		||||
    def __await__(self):
 | 
			
		||||
        resp = yield from self._coro
 | 
			
		||||
        return resp
 | 
			
		||||
 | 
			
		||||
    async def __anext__(self):
 | 
			
		||||
        resp = await self._coro
 | 
			
		||||
        return resp
 | 
			
		||||
 | 
			
		||||
    async def __aenter__(self):
 | 
			
		||||
        self._obj = await self._coro
 | 
			
		||||
        return self._obj
 | 
			
		||||
 | 
			
		||||
    async def __aexit__(self, exc_type, exc, tb):
 | 
			
		||||
        self._obj.close()
 | 
			
		||||
        self._obj = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AiofilesContextManager(_ContextManager):
 | 
			
		||||
    """An adjusted async context manager for aiofiles."""
 | 
			
		||||
 | 
			
		||||
    async def __aexit__(self, exc_type, exc_val, exc_tb):
 | 
			
		||||
        await self._obj.close()
 | 
			
		||||
        self._obj = None
 | 
			
		||||
							
								
								
									
										31
									
								
								deps/lib/python3.10/site-packages/aiofiles/os.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								deps/lib/python3.10/site-packages/aiofiles/os.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
"""Async executor versions of file functions from the os module."""
 | 
			
		||||
import asyncio
 | 
			
		||||
from functools import partial, wraps
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def wrap(func):
 | 
			
		||||
    @wraps(func)
 | 
			
		||||
    async def run(*args, loop=None, executor=None, **kwargs):
 | 
			
		||||
        if loop is None:
 | 
			
		||||
            loop = asyncio.get_event_loop()
 | 
			
		||||
        pfunc = partial(func, *args, **kwargs)
 | 
			
		||||
        return await loop.run_in_executor(executor, pfunc)
 | 
			
		||||
 | 
			
		||||
    return run
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from . import ospath as path
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
stat = wrap(os.stat)
 | 
			
		||||
rename = wrap(os.rename)
 | 
			
		||||
replace = wrap(os.replace)
 | 
			
		||||
remove = wrap(os.remove)
 | 
			
		||||
mkdir = wrap(os.mkdir)
 | 
			
		||||
makedirs = wrap(os.makedirs)
 | 
			
		||||
rmdir = wrap(os.rmdir)
 | 
			
		||||
removedirs = wrap(os.removedirs)
 | 
			
		||||
 | 
			
		||||
if hasattr(os, "sendfile"):
 | 
			
		||||
    sendfile = wrap(os.sendfile)
 | 
			
		||||
							
								
								
									
										14
									
								
								deps/lib/python3.10/site-packages/aiofiles/ospath.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								deps/lib/python3.10/site-packages/aiofiles/ospath.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
"""Async executor versions of file functions from the os.path module."""
 | 
			
		||||
 | 
			
		||||
from .os import wrap
 | 
			
		||||
from os import path
 | 
			
		||||
 | 
			
		||||
exists = wrap(path.exists)
 | 
			
		||||
isfile = wrap(path.isfile)
 | 
			
		||||
isdir = wrap(path.isdir)
 | 
			
		||||
getsize = wrap(path.getsize)
 | 
			
		||||
getmtime = wrap(path.getmtime)
 | 
			
		||||
getatime = wrap(path.getatime)
 | 
			
		||||
getctime = wrap(path.getctime)
 | 
			
		||||
samefile = wrap(path.samefile)
 | 
			
		||||
sameopenfile = wrap(path.sameopenfile)
 | 
			
		||||
							
								
								
									
										263
									
								
								deps/lib/python3.10/site-packages/aiofiles/tempfile/__init__.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								deps/lib/python3.10/site-packages/aiofiles/tempfile/__init__.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,263 @@
 | 
			
		||||
# Imports
 | 
			
		||||
import asyncio
 | 
			
		||||
from tempfile import (
 | 
			
		||||
    TemporaryFile as syncTemporaryFile,
 | 
			
		||||
    NamedTemporaryFile as syncNamedTemporaryFile,
 | 
			
		||||
    SpooledTemporaryFile as syncSpooledTemporaryFile,
 | 
			
		||||
    TemporaryDirectory as syncTemporaryDirectory,
 | 
			
		||||
    _TemporaryFileWrapper as syncTemporaryFileWrapper,
 | 
			
		||||
)
 | 
			
		||||
from io import FileIO, TextIOBase, BufferedReader, BufferedWriter, BufferedRandom
 | 
			
		||||
from functools import partial, singledispatch
 | 
			
		||||
from ..base import AiofilesContextManager
 | 
			
		||||
from ..threadpool.text import AsyncTextIOWrapper
 | 
			
		||||
from ..threadpool.binary import AsyncBufferedIOBase, AsyncBufferedReader, AsyncFileIO
 | 
			
		||||
from .temptypes import AsyncSpooledTemporaryFile, AsyncTemporaryDirectory
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
    "NamedTemporaryFile",
 | 
			
		||||
    "TemporaryFile",
 | 
			
		||||
    "SpooledTemporaryFile",
 | 
			
		||||
    "TemporaryDirectory",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ================================================================
 | 
			
		||||
# Public methods for async open and return of temp file/directory
 | 
			
		||||
# objects with async interface
 | 
			
		||||
# ================================================================
 | 
			
		||||
def NamedTemporaryFile(
 | 
			
		||||
    mode="w+b",
 | 
			
		||||
    buffering=-1,
 | 
			
		||||
    encoding=None,
 | 
			
		||||
    newline=None,
 | 
			
		||||
    suffix=None,
 | 
			
		||||
    prefix=None,
 | 
			
		||||
    dir=None,
 | 
			
		||||
    delete=True,
 | 
			
		||||
    loop=None,
 | 
			
		||||
    executor=None,
 | 
			
		||||
):
 | 
			
		||||
    """Async open a named temporary file"""
 | 
			
		||||
    return AiofilesContextManager(
 | 
			
		||||
        _temporary_file(
 | 
			
		||||
            named=True,
 | 
			
		||||
            mode=mode,
 | 
			
		||||
            buffering=buffering,
 | 
			
		||||
            encoding=encoding,
 | 
			
		||||
            newline=newline,
 | 
			
		||||
            suffix=suffix,
 | 
			
		||||
            prefix=prefix,
 | 
			
		||||
            dir=dir,
 | 
			
		||||
            delete=delete,
 | 
			
		||||
            loop=loop,
 | 
			
		||||
            executor=executor,
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def TemporaryFile(
 | 
			
		||||
    mode="w+b",
 | 
			
		||||
    buffering=-1,
 | 
			
		||||
    encoding=None,
 | 
			
		||||
    newline=None,
 | 
			
		||||
    suffix=None,
 | 
			
		||||
    prefix=None,
 | 
			
		||||
    dir=None,
 | 
			
		||||
    loop=None,
 | 
			
		||||
    executor=None,
 | 
			
		||||
):
 | 
			
		||||
    """Async open an unnamed temporary file"""
 | 
			
		||||
    return AiofilesContextManager(
 | 
			
		||||
        _temporary_file(
 | 
			
		||||
            named=False,
 | 
			
		||||
            mode=mode,
 | 
			
		||||
            buffering=buffering,
 | 
			
		||||
            encoding=encoding,
 | 
			
		||||
            newline=newline,
 | 
			
		||||
            suffix=suffix,
 | 
			
		||||
            prefix=prefix,
 | 
			
		||||
            dir=dir,
 | 
			
		||||
            loop=loop,
 | 
			
		||||
            executor=executor,
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def SpooledTemporaryFile(
 | 
			
		||||
    max_size=0,
 | 
			
		||||
    mode="w+b",
 | 
			
		||||
    buffering=-1,
 | 
			
		||||
    encoding=None,
 | 
			
		||||
    newline=None,
 | 
			
		||||
    suffix=None,
 | 
			
		||||
    prefix=None,
 | 
			
		||||
    dir=None,
 | 
			
		||||
    loop=None,
 | 
			
		||||
    executor=None,
 | 
			
		||||
):
 | 
			
		||||
    """Async open a spooled temporary file"""
 | 
			
		||||
    return AiofilesContextManager(
 | 
			
		||||
        _spooled_temporary_file(
 | 
			
		||||
            max_size=max_size,
 | 
			
		||||
            mode=mode,
 | 
			
		||||
            buffering=buffering,
 | 
			
		||||
            encoding=encoding,
 | 
			
		||||
            newline=newline,
 | 
			
		||||
            suffix=suffix,
 | 
			
		||||
            prefix=prefix,
 | 
			
		||||
            dir=dir,
 | 
			
		||||
            loop=loop,
 | 
			
		||||
            executor=executor,
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def TemporaryDirectory(suffix=None, prefix=None, dir=None, loop=None, executor=None):
 | 
			
		||||
    """Async open a temporary directory"""
 | 
			
		||||
    return AiofilesContextManagerTempDir(
 | 
			
		||||
        _temporary_directory(
 | 
			
		||||
            suffix=suffix, prefix=prefix, dir=dir, loop=loop, executor=executor
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# =========================================================
 | 
			
		||||
# Internal coroutines to open new temp files/directories
 | 
			
		||||
# =========================================================
 | 
			
		||||
async def _temporary_file(
 | 
			
		||||
    named=True,
 | 
			
		||||
    mode="w+b",
 | 
			
		||||
    buffering=-1,
 | 
			
		||||
    encoding=None,
 | 
			
		||||
    newline=None,
 | 
			
		||||
    suffix=None,
 | 
			
		||||
    prefix=None,
 | 
			
		||||
    dir=None,
 | 
			
		||||
    delete=True,
 | 
			
		||||
    loop=None,
 | 
			
		||||
    executor=None,
 | 
			
		||||
    max_size=0,
 | 
			
		||||
):
 | 
			
		||||
    """Async method to open a temporary file with async interface"""
 | 
			
		||||
    if loop is None:
 | 
			
		||||
        loop = asyncio.get_event_loop()
 | 
			
		||||
 | 
			
		||||
    if named:
 | 
			
		||||
        cb = partial(
 | 
			
		||||
            syncNamedTemporaryFile,
 | 
			
		||||
            mode=mode,
 | 
			
		||||
            buffering=buffering,
 | 
			
		||||
            encoding=encoding,
 | 
			
		||||
            newline=newline,
 | 
			
		||||
            suffix=suffix,
 | 
			
		||||
            prefix=prefix,
 | 
			
		||||
            dir=dir,
 | 
			
		||||
            delete=delete,
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        cb = partial(
 | 
			
		||||
            syncTemporaryFile,
 | 
			
		||||
            mode=mode,
 | 
			
		||||
            buffering=buffering,
 | 
			
		||||
            encoding=encoding,
 | 
			
		||||
            newline=newline,
 | 
			
		||||
            suffix=suffix,
 | 
			
		||||
            prefix=prefix,
 | 
			
		||||
            dir=dir,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    f = await loop.run_in_executor(executor, cb)
 | 
			
		||||
 | 
			
		||||
    # Wrap based on type of underlying IO object
 | 
			
		||||
    if type(f) is syncTemporaryFileWrapper:
 | 
			
		||||
        # _TemporaryFileWrapper was used (named files)
 | 
			
		||||
        result = wrap(f.file, f, loop=loop, executor=executor)
 | 
			
		||||
        # add delete property
 | 
			
		||||
        result.delete = f.delete
 | 
			
		||||
        return result
 | 
			
		||||
    else:
 | 
			
		||||
        # IO object was returned directly without wrapper
 | 
			
		||||
        return wrap(f, f, loop=loop, executor=executor)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def _spooled_temporary_file(
 | 
			
		||||
    max_size=0,
 | 
			
		||||
    mode="w+b",
 | 
			
		||||
    buffering=-1,
 | 
			
		||||
    encoding=None,
 | 
			
		||||
    newline=None,
 | 
			
		||||
    suffix=None,
 | 
			
		||||
    prefix=None,
 | 
			
		||||
    dir=None,
 | 
			
		||||
    loop=None,
 | 
			
		||||
    executor=None,
 | 
			
		||||
):
 | 
			
		||||
    """Open a spooled temporary file with async interface"""
 | 
			
		||||
    if loop is None:
 | 
			
		||||
        loop = asyncio.get_event_loop()
 | 
			
		||||
 | 
			
		||||
    cb = partial(
 | 
			
		||||
        syncSpooledTemporaryFile,
 | 
			
		||||
        max_size=max_size,
 | 
			
		||||
        mode=mode,
 | 
			
		||||
        buffering=buffering,
 | 
			
		||||
        encoding=encoding,
 | 
			
		||||
        newline=newline,
 | 
			
		||||
        suffix=suffix,
 | 
			
		||||
        prefix=prefix,
 | 
			
		||||
        dir=dir,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    f = await loop.run_in_executor(executor, cb)
 | 
			
		||||
 | 
			
		||||
    # Single interface provided by SpooledTemporaryFile for all modes
 | 
			
		||||
    return AsyncSpooledTemporaryFile(f, loop=loop, executor=executor)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def _temporary_directory(
 | 
			
		||||
    suffix=None, prefix=None, dir=None, loop=None, executor=None
 | 
			
		||||
):
 | 
			
		||||
    """Async method to open a temporary directory with async interface"""
 | 
			
		||||
    if loop is None:
 | 
			
		||||
        loop = asyncio.get_event_loop()
 | 
			
		||||
 | 
			
		||||
    cb = partial(syncTemporaryDirectory, suffix, prefix, dir)
 | 
			
		||||
    f = await loop.run_in_executor(executor, cb)
 | 
			
		||||
 | 
			
		||||
    return AsyncTemporaryDirectory(f, loop=loop, executor=executor)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AiofilesContextManagerTempDir(AiofilesContextManager):
 | 
			
		||||
    """With returns the directory location, not the object (matching sync lib)"""
 | 
			
		||||
 | 
			
		||||
    async def __aenter__(self):
 | 
			
		||||
        self._obj = await self._coro
 | 
			
		||||
        return self._obj.name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@singledispatch
 | 
			
		||||
def wrap(base_io_obj, file, *, loop=None, executor=None):
 | 
			
		||||
    """Wrap the object with interface based on type of underlying IO"""
 | 
			
		||||
    raise TypeError("Unsupported IO type: {}".format(base_io_obj))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@wrap.register(TextIOBase)
 | 
			
		||||
def _(base_io_obj, file, *, loop=None, executor=None):
 | 
			
		||||
    return AsyncTextIOWrapper(file, loop=loop, executor=executor)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@wrap.register(BufferedWriter)
 | 
			
		||||
def _(base_io_obj, file, *, loop=None, executor=None):
 | 
			
		||||
    return AsyncBufferedIOBase(file, loop=loop, executor=executor)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@wrap.register(BufferedReader)
 | 
			
		||||
@wrap.register(BufferedRandom)
 | 
			
		||||
def _(base_io_obj, file, *, loop=None, executor=None):
 | 
			
		||||
    return AsyncBufferedReader(file, loop=loop, executor=executor)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@wrap.register(FileIO)
 | 
			
		||||
def _(base_io_obj, file, *, loop=None, executor=None):
 | 
			
		||||
    return AsyncFileIO(file, loop=loop, executor=executor)
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/tempfile/__pycache__/__init__.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/tempfile/__pycache__/__init__.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/tempfile/__pycache__/temptypes.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/tempfile/__pycache__/temptypes.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										74
									
								
								deps/lib/python3.10/site-packages/aiofiles/tempfile/temptypes.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								deps/lib/python3.10/site-packages/aiofiles/tempfile/temptypes.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
"""Async wrappers for spooled temp files and temp directory objects"""
 | 
			
		||||
 | 
			
		||||
# Imports
 | 
			
		||||
import asyncio
 | 
			
		||||
from types import coroutine
 | 
			
		||||
 | 
			
		||||
from ..base import AsyncBase
 | 
			
		||||
from ..threadpool.utils import (
 | 
			
		||||
    delegate_to_executor,
 | 
			
		||||
    proxy_property_directly,
 | 
			
		||||
    cond_delegate_to_executor,
 | 
			
		||||
)
 | 
			
		||||
from functools import partial
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@delegate_to_executor("fileno", "rollover")
 | 
			
		||||
@cond_delegate_to_executor(
 | 
			
		||||
    "close",
 | 
			
		||||
    "flush",
 | 
			
		||||
    "isatty",
 | 
			
		||||
    "newlines",
 | 
			
		||||
    "read",
 | 
			
		||||
    "readline",
 | 
			
		||||
    "readlines",
 | 
			
		||||
    "seek",
 | 
			
		||||
    "tell",
 | 
			
		||||
    "truncate",
 | 
			
		||||
)
 | 
			
		||||
@proxy_property_directly("closed", "encoding", "mode", "name", "softspace")
 | 
			
		||||
class AsyncSpooledTemporaryFile(AsyncBase):
 | 
			
		||||
    """Async wrapper for SpooledTemporaryFile class"""
 | 
			
		||||
 | 
			
		||||
    async def _check(self):
 | 
			
		||||
        if self._file._rolled:
 | 
			
		||||
            return
 | 
			
		||||
        max_size = self._file._max_size
 | 
			
		||||
        if max_size and self._file.tell() > max_size:
 | 
			
		||||
            await self.rollover()
 | 
			
		||||
 | 
			
		||||
    async def write(self, s):
 | 
			
		||||
        """Implementation to anticipate rollover"""
 | 
			
		||||
        if self._file._rolled:
 | 
			
		||||
            cb = partial(self._file.write, s)
 | 
			
		||||
            return await self._loop.run_in_executor(self._executor, cb)
 | 
			
		||||
        else:
 | 
			
		||||
            file = self._file._file  # reference underlying base IO object
 | 
			
		||||
            rv = file.write(s)
 | 
			
		||||
            await self._check()
 | 
			
		||||
            return rv
 | 
			
		||||
 | 
			
		||||
    async def writelines(self, iterable):
 | 
			
		||||
        """Implementation to anticipate rollover"""
 | 
			
		||||
        if self._file._rolled:
 | 
			
		||||
            cb = partial(self._file.writelines, iterable)
 | 
			
		||||
            return await self._loop.run_in_executor(self._executor, cb)
 | 
			
		||||
        else:
 | 
			
		||||
            file = self._file._file  # reference underlying base IO object
 | 
			
		||||
            rv = file.writelines(iterable)
 | 
			
		||||
            await self._check()
 | 
			
		||||
            return rv
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@delegate_to_executor("cleanup")
 | 
			
		||||
@proxy_property_directly("name")
 | 
			
		||||
class AsyncTemporaryDirectory:
 | 
			
		||||
    """Async wrapper for TemporaryDirectory class"""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, file, loop, executor):
 | 
			
		||||
        self._file = file
 | 
			
		||||
        self._loop = loop
 | 
			
		||||
        self._executor = executor
 | 
			
		||||
 | 
			
		||||
    async def close(self):
 | 
			
		||||
        await self.cleanup()
 | 
			
		||||
							
								
								
									
										108
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/__init__.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/__init__.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
"""Handle files using a thread pool executor."""
 | 
			
		||||
import asyncio
 | 
			
		||||
from types import coroutine
 | 
			
		||||
 | 
			
		||||
from io import (
 | 
			
		||||
    FileIO,
 | 
			
		||||
    TextIOBase,
 | 
			
		||||
    BufferedReader,
 | 
			
		||||
    BufferedWriter,
 | 
			
		||||
    BufferedRandom,
 | 
			
		||||
)
 | 
			
		||||
from functools import partial, singledispatch
 | 
			
		||||
 | 
			
		||||
from .binary import AsyncBufferedIOBase, AsyncBufferedReader, AsyncFileIO
 | 
			
		||||
from .text import AsyncTextIOWrapper
 | 
			
		||||
from ..base import AiofilesContextManager
 | 
			
		||||
 | 
			
		||||
sync_open = open
 | 
			
		||||
 | 
			
		||||
__all__ = ("open",)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def open(
 | 
			
		||||
    file,
 | 
			
		||||
    mode="r",
 | 
			
		||||
    buffering=-1,
 | 
			
		||||
    encoding=None,
 | 
			
		||||
    errors=None,
 | 
			
		||||
    newline=None,
 | 
			
		||||
    closefd=True,
 | 
			
		||||
    opener=None,
 | 
			
		||||
    *,
 | 
			
		||||
    loop=None,
 | 
			
		||||
    executor=None
 | 
			
		||||
):
 | 
			
		||||
    return AiofilesContextManager(
 | 
			
		||||
        _open(
 | 
			
		||||
            file,
 | 
			
		||||
            mode=mode,
 | 
			
		||||
            buffering=buffering,
 | 
			
		||||
            encoding=encoding,
 | 
			
		||||
            errors=errors,
 | 
			
		||||
            newline=newline,
 | 
			
		||||
            closefd=closefd,
 | 
			
		||||
            opener=opener,
 | 
			
		||||
            loop=loop,
 | 
			
		||||
            executor=executor,
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine
 | 
			
		||||
def _open(
 | 
			
		||||
    file,
 | 
			
		||||
    mode="r",
 | 
			
		||||
    buffering=-1,
 | 
			
		||||
    encoding=None,
 | 
			
		||||
    errors=None,
 | 
			
		||||
    newline=None,
 | 
			
		||||
    closefd=True,
 | 
			
		||||
    opener=None,
 | 
			
		||||
    *,
 | 
			
		||||
    loop=None,
 | 
			
		||||
    executor=None
 | 
			
		||||
):
 | 
			
		||||
    """Open an asyncio file."""
 | 
			
		||||
    if loop is None:
 | 
			
		||||
        loop = asyncio.get_event_loop()
 | 
			
		||||
    cb = partial(
 | 
			
		||||
        sync_open,
 | 
			
		||||
        file,
 | 
			
		||||
        mode=mode,
 | 
			
		||||
        buffering=buffering,
 | 
			
		||||
        encoding=encoding,
 | 
			
		||||
        errors=errors,
 | 
			
		||||
        newline=newline,
 | 
			
		||||
        closefd=closefd,
 | 
			
		||||
        opener=opener,
 | 
			
		||||
    )
 | 
			
		||||
    f = yield from loop.run_in_executor(executor, cb)
 | 
			
		||||
 | 
			
		||||
    return wrap(f, loop=loop, executor=executor)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@singledispatch
 | 
			
		||||
def wrap(file, *, loop=None, executor=None):
 | 
			
		||||
    raise TypeError("Unsupported io type: {}.".format(file))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@wrap.register(TextIOBase)
 | 
			
		||||
def _(file, *, loop=None, executor=None):
 | 
			
		||||
    return AsyncTextIOWrapper(file, loop=loop, executor=executor)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@wrap.register(BufferedWriter)
 | 
			
		||||
def _(file, *, loop=None, executor=None):
 | 
			
		||||
    return AsyncBufferedIOBase(file, loop=loop, executor=executor)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@wrap.register(BufferedReader)
 | 
			
		||||
@wrap.register(BufferedRandom)
 | 
			
		||||
def _(file, *, loop=None, executor=None):
 | 
			
		||||
    return AsyncBufferedReader(file, loop=loop, executor=executor)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@wrap.register(FileIO)
 | 
			
		||||
def _(file, *, loop=None, executor=None):
 | 
			
		||||
    return AsyncFileIO(file, loop, executor)
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/__pycache__/__init__.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/__pycache__/__init__.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/__pycache__/binary.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/__pycache__/binary.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/__pycache__/text.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/__pycache__/text.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/__pycache__/utils.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/__pycache__/utils.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										57
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/binary.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/binary.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
from ..base import AsyncBase
 | 
			
		||||
from .utils import (
 | 
			
		||||
    delegate_to_executor,
 | 
			
		||||
    proxy_method_directly,
 | 
			
		||||
    proxy_property_directly,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@delegate_to_executor(
 | 
			
		||||
    "close",
 | 
			
		||||
    "flush",
 | 
			
		||||
    "isatty",
 | 
			
		||||
    "read",
 | 
			
		||||
    "read1",
 | 
			
		||||
    "readinto",
 | 
			
		||||
    "readline",
 | 
			
		||||
    "readlines",
 | 
			
		||||
    "seek",
 | 
			
		||||
    "seekable",
 | 
			
		||||
    "tell",
 | 
			
		||||
    "truncate",
 | 
			
		||||
    "writable",
 | 
			
		||||
    "write",
 | 
			
		||||
    "writelines",
 | 
			
		||||
)
 | 
			
		||||
@proxy_method_directly("detach", "fileno", "readable")
 | 
			
		||||
@proxy_property_directly("closed", "raw", "name", "mode")
 | 
			
		||||
class AsyncBufferedIOBase(AsyncBase):
 | 
			
		||||
    """The asyncio executor version of io.BufferedWriter."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@delegate_to_executor("peek")
 | 
			
		||||
class AsyncBufferedReader(AsyncBufferedIOBase):
 | 
			
		||||
    """The asyncio executor version of io.BufferedReader and Random."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@delegate_to_executor(
 | 
			
		||||
    "close",
 | 
			
		||||
    "flush",
 | 
			
		||||
    "isatty",
 | 
			
		||||
    "read",
 | 
			
		||||
    "readall",
 | 
			
		||||
    "readinto",
 | 
			
		||||
    "readline",
 | 
			
		||||
    "readlines",
 | 
			
		||||
    "seek",
 | 
			
		||||
    "seekable",
 | 
			
		||||
    "tell",
 | 
			
		||||
    "truncate",
 | 
			
		||||
    "writable",
 | 
			
		||||
    "write",
 | 
			
		||||
    "writelines",
 | 
			
		||||
)
 | 
			
		||||
@proxy_method_directly("fileno", "readable")
 | 
			
		||||
@proxy_property_directly("closed", "name", "mode")
 | 
			
		||||
class AsyncFileIO(AsyncBase):
 | 
			
		||||
    """The asyncio executor version of io.FileIO."""
 | 
			
		||||
							
								
								
									
										37
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/text.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/text.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
from ..base import AsyncBase
 | 
			
		||||
from .utils import (
 | 
			
		||||
    delegate_to_executor,
 | 
			
		||||
    proxy_method_directly,
 | 
			
		||||
    proxy_property_directly,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@delegate_to_executor(
 | 
			
		||||
    "close",
 | 
			
		||||
    "flush",
 | 
			
		||||
    "isatty",
 | 
			
		||||
    "read",
 | 
			
		||||
    "readable",
 | 
			
		||||
    "readline",
 | 
			
		||||
    "readlines",
 | 
			
		||||
    "seek",
 | 
			
		||||
    "seekable",
 | 
			
		||||
    "tell",
 | 
			
		||||
    "truncate",
 | 
			
		||||
    "write",
 | 
			
		||||
    "writable",
 | 
			
		||||
    "writelines",
 | 
			
		||||
)
 | 
			
		||||
@proxy_method_directly("detach", "fileno", "readable")
 | 
			
		||||
@proxy_property_directly(
 | 
			
		||||
    "buffer",
 | 
			
		||||
    "closed",
 | 
			
		||||
    "encoding",
 | 
			
		||||
    "errors",
 | 
			
		||||
    "line_buffering",
 | 
			
		||||
    "newlines",
 | 
			
		||||
    "name",
 | 
			
		||||
    "mode",
 | 
			
		||||
)
 | 
			
		||||
class AsyncTextIOWrapper(AsyncBase):
 | 
			
		||||
    """The asyncio executor version of io.TextIOWrapper."""
 | 
			
		||||
							
								
								
									
										74
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/utils.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								deps/lib/python3.10/site-packages/aiofiles/threadpool/utils.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
import functools
 | 
			
		||||
from types import coroutine
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def delegate_to_executor(*attrs):
 | 
			
		||||
    def cls_builder(cls):
 | 
			
		||||
        for attr_name in attrs:
 | 
			
		||||
            setattr(cls, attr_name, _make_delegate_method(attr_name))
 | 
			
		||||
        return cls
 | 
			
		||||
 | 
			
		||||
    return cls_builder
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def proxy_method_directly(*attrs):
 | 
			
		||||
    def cls_builder(cls):
 | 
			
		||||
        for attr_name in attrs:
 | 
			
		||||
            setattr(cls, attr_name, _make_proxy_method(attr_name))
 | 
			
		||||
        return cls
 | 
			
		||||
 | 
			
		||||
    return cls_builder
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def proxy_property_directly(*attrs):
 | 
			
		||||
    def cls_builder(cls):
 | 
			
		||||
        for attr_name in attrs:
 | 
			
		||||
            setattr(cls, attr_name, _make_proxy_property(attr_name))
 | 
			
		||||
        return cls
 | 
			
		||||
 | 
			
		||||
    return cls_builder
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def cond_delegate_to_executor(*attrs):
 | 
			
		||||
    def cls_builder(cls):
 | 
			
		||||
        for attr_name in attrs:
 | 
			
		||||
            setattr(cls, attr_name, _make_cond_delegate_method(attr_name))
 | 
			
		||||
        return cls
 | 
			
		||||
 | 
			
		||||
    return cls_builder
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _make_delegate_method(attr_name):
 | 
			
		||||
    @coroutine
 | 
			
		||||
    def method(self, *args, **kwargs):
 | 
			
		||||
        cb = functools.partial(getattr(self._file, attr_name), *args, **kwargs)
 | 
			
		||||
        return (yield from self._loop.run_in_executor(self._executor, cb))
 | 
			
		||||
 | 
			
		||||
    return method
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _make_proxy_method(attr_name):
 | 
			
		||||
    def method(self, *args, **kwargs):
 | 
			
		||||
        return getattr(self._file, attr_name)(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    return method
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _make_proxy_property(attr_name):
 | 
			
		||||
    def proxy_property(self):
 | 
			
		||||
        return getattr(self._file, attr_name)
 | 
			
		||||
 | 
			
		||||
    return property(proxy_property)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _make_cond_delegate_method(attr_name):
 | 
			
		||||
    """For spooled temp files, delegate only if rolled to file object"""
 | 
			
		||||
 | 
			
		||||
    async def method(self, *args, **kwargs):
 | 
			
		||||
        if self._file._rolled:
 | 
			
		||||
            cb = functools.partial(getattr(self._file, attr_name), *args, **kwargs)
 | 
			
		||||
            return await self._loop.run_in_executor(self._executor, cb)
 | 
			
		||||
        else:
 | 
			
		||||
            return getattr(self._file, attr_name)(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    return method
 | 
			
		||||
							
								
								
									
										7
									
								
								deps/lib/python3.10/site-packages/eq3bt/__init__.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								deps/lib/python3.10/site-packages/eq3bt/__init__.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
# flake8: noqa
 | 
			
		||||
from .eq3btsmart import Mode, TemperatureException, Thermostat
 | 
			
		||||
from .structures import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BackendException(Exception):
 | 
			
		||||
    """Exception to wrap backend exceptions."""
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/__init__.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/__init__.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/bleakconnection.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/bleakconnection.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/connection.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/connection.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/eq3btsmart.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/eq3btsmart.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/eq3cli.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/eq3cli.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/gattlibconnection.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/gattlibconnection.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/structures.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/__pycache__/structures.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										123
									
								
								deps/lib/python3.10/site-packages/eq3bt/bleakconnection.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								deps/lib/python3.10/site-packages/eq3bt/bleakconnection.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
"""
 | 
			
		||||
Bleak connection backend.
 | 
			
		||||
This creates a new event loop that is used to integrate bleak's
 | 
			
		||||
asyncio functions to synchronous architecture of python-eq3bt.
 | 
			
		||||
"""
 | 
			
		||||
import asyncio
 | 
			
		||||
import codecs
 | 
			
		||||
import contextlib
 | 
			
		||||
import logging
 | 
			
		||||
from typing import Optional
 | 
			
		||||
 | 
			
		||||
from bleak import BleakClient, BleakError
 | 
			
		||||
 | 
			
		||||
from . import BackendException
 | 
			
		||||
 | 
			
		||||
DEFAULT_TIMEOUT = 1
 | 
			
		||||
 | 
			
		||||
# bleak backends are very loud on debug, this reduces the log spam when using --debug
 | 
			
		||||
logging.getLogger("bleak.backends").setLevel(logging.WARNING)
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BleakConnection:
 | 
			
		||||
    """Representation of a BTLE Connection."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, mac, iface):
 | 
			
		||||
        """Initialize the connection."""
 | 
			
		||||
 | 
			
		||||
        self._conn: Optional[BleakClient] = None
 | 
			
		||||
        self._mac = mac
 | 
			
		||||
        self._iface = iface
 | 
			
		||||
        self._callbacks = {}
 | 
			
		||||
        self._notifyevent = asyncio.Event()
 | 
			
		||||
        self._notification_handle = None
 | 
			
		||||
        self._loop = asyncio.new_event_loop()
 | 
			
		||||
 | 
			
		||||
    def __enter__(self):
 | 
			
		||||
        """
 | 
			
		||||
        Context manager __enter__ for connecting the device
 | 
			
		||||
        :rtype: BTLEConnection
 | 
			
		||||
        :return:
 | 
			
		||||
        """
 | 
			
		||||
        _LOGGER.debug("Trying to connect to %s", self._mac)
 | 
			
		||||
 | 
			
		||||
        kwargs = {}
 | 
			
		||||
        if self._iface is not None:
 | 
			
		||||
            kwargs["adapter"] = self._iface
 | 
			
		||||
        self._conn = BleakClient(self._mac, **kwargs)
 | 
			
		||||
        try:
 | 
			
		||||
            self._loop.run_until_complete(self._conn.connect())
 | 
			
		||||
        except BleakError as ex:
 | 
			
		||||
            _LOGGER.debug(
 | 
			
		||||
                "Unable to connect to the device %s, retrying: %s", self._mac, ex
 | 
			
		||||
            )
 | 
			
		||||
            try:
 | 
			
		||||
                self._loop.run_until_complete(self._conn.connect())
 | 
			
		||||
            except Exception as ex2:
 | 
			
		||||
                _LOGGER.debug("Second connection try to %s failed: %s", self._mac, ex2)
 | 
			
		||||
                raise BackendException(
 | 
			
		||||
                    "unable to connect to device using bleak"
 | 
			
		||||
                ) from ex2
 | 
			
		||||
 | 
			
		||||
        # The notification handles are off-by-one compared to gattlib and bluepy
 | 
			
		||||
        self._loop.run_until_complete(
 | 
			
		||||
            self._conn.start_notify(self._notification_handle - 1, self.on_notification)
 | 
			
		||||
        )
 | 
			
		||||
        _LOGGER.debug("Connected to %s", self._mac)
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def __exit__(self, exc_type, exc_val, exc_tb):
 | 
			
		||||
        if self._conn:
 | 
			
		||||
            self._loop.run_until_complete(self._conn.disconnect())
 | 
			
		||||
            self._conn = None
 | 
			
		||||
 | 
			
		||||
    async def on_notification(self, handle, data):
 | 
			
		||||
        """Handle Callback from a Bluetooth (GATT) request."""
 | 
			
		||||
        # The notification handles are off-by-one compared to gattlib and bluepy
 | 
			
		||||
        handle = handle + 1
 | 
			
		||||
        _LOGGER.debug(
 | 
			
		||||
            "Got notification from %s: %s", handle, codecs.encode(data, "hex")
 | 
			
		||||
        )
 | 
			
		||||
        self._notifyevent.set()
 | 
			
		||||
 | 
			
		||||
        if handle in self._callbacks:
 | 
			
		||||
            self._callbacks[handle](data)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def mac(self):
 | 
			
		||||
        """Return the MAC address of the connected device."""
 | 
			
		||||
        return self._mac
 | 
			
		||||
 | 
			
		||||
    def set_callback(self, handle, function):
 | 
			
		||||
        """Set the callback for a Notification handle. It will be called with the parameter data, which is binary."""
 | 
			
		||||
        self._notification_handle = handle
 | 
			
		||||
        self._callbacks[handle] = function
 | 
			
		||||
 | 
			
		||||
    async def wait_for_response(self, timeout):
 | 
			
		||||
        with contextlib.suppress(asyncio.TimeoutError):
 | 
			
		||||
            await asyncio.wait_for(self._notifyevent.wait(), timeout)
 | 
			
		||||
 | 
			
		||||
    def make_request(self, handle, value, timeout=DEFAULT_TIMEOUT, with_response=True):
 | 
			
		||||
        """Write a GATT Command without callback - not utf-8."""
 | 
			
		||||
        try:
 | 
			
		||||
            with self:
 | 
			
		||||
                _LOGGER.debug(
 | 
			
		||||
                    "Writing %s to %s",
 | 
			
		||||
                    codecs.encode(value, "hex"),
 | 
			
		||||
                    handle,
 | 
			
		||||
                )
 | 
			
		||||
                self._notifyevent.clear()
 | 
			
		||||
 | 
			
		||||
                self._loop.run_until_complete(
 | 
			
		||||
                    self._conn.write_gatt_char(handle - 1, value)
 | 
			
		||||
                )
 | 
			
		||||
                if timeout:
 | 
			
		||||
                    _LOGGER.debug("Waiting for notifications for %s", timeout)
 | 
			
		||||
                    self._loop.run_until_complete(self.wait_for_response(timeout))
 | 
			
		||||
 | 
			
		||||
        except BleakError as ex:
 | 
			
		||||
            _LOGGER.debug("Got exception from bleak while making a request: %s", ex)
 | 
			
		||||
            raise BackendException("Exception on write using bleak") from ex
 | 
			
		||||
							
								
								
									
										95
									
								
								deps/lib/python3.10/site-packages/eq3bt/connection.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								deps/lib/python3.10/site-packages/eq3bt/connection.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
"""
 | 
			
		||||
A simple wrapper for bluepy's btle.Connection.
 | 
			
		||||
Handles Connection duties (reconnecting etc.) transparently.
 | 
			
		||||
"""
 | 
			
		||||
import codecs
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from bluepy import btle
 | 
			
		||||
 | 
			
		||||
from . import BackendException
 | 
			
		||||
 | 
			
		||||
DEFAULT_TIMEOUT = 1
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BTLEConnection(btle.DefaultDelegate):
 | 
			
		||||
    """Representation of a BTLE Connection."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, mac, iface):
 | 
			
		||||
        """Initialize the connection."""
 | 
			
		||||
        btle.DefaultDelegate.__init__(self)
 | 
			
		||||
 | 
			
		||||
        self._conn = None
 | 
			
		||||
        self._mac = mac
 | 
			
		||||
        self._iface = iface
 | 
			
		||||
        self._callbacks = {}
 | 
			
		||||
 | 
			
		||||
    def __enter__(self):
 | 
			
		||||
        """
 | 
			
		||||
        Context manager __enter__ for connecting the device
 | 
			
		||||
        :rtype: btle.Peripheral
 | 
			
		||||
        :return:
 | 
			
		||||
        """
 | 
			
		||||
        self._conn = btle.Peripheral()
 | 
			
		||||
        self._conn.withDelegate(self)
 | 
			
		||||
        _LOGGER.debug("Trying to connect to %s", self._mac)
 | 
			
		||||
        try:
 | 
			
		||||
            self._conn.connect(self._mac, iface=self._iface)
 | 
			
		||||
        except btle.BTLEException as ex:
 | 
			
		||||
            _LOGGER.debug(
 | 
			
		||||
                "Unable to connect to the device %s, retrying: %s", self._mac, ex
 | 
			
		||||
            )
 | 
			
		||||
            try:
 | 
			
		||||
                self._conn.connect(self._mac, iface=self._iface)
 | 
			
		||||
            except Exception as ex2:
 | 
			
		||||
                _LOGGER.debug("Second connection try to %s failed: %s", self._mac, ex2)
 | 
			
		||||
                raise BackendException(
 | 
			
		||||
                    "Unable to connect to device using bluepy"
 | 
			
		||||
                ) from ex2
 | 
			
		||||
 | 
			
		||||
        _LOGGER.debug("Connected to %s", self._mac)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def __exit__(self, exc_type, exc_val, exc_tb):
 | 
			
		||||
        if self._conn:
 | 
			
		||||
            self._conn.disconnect()
 | 
			
		||||
            self._conn = None
 | 
			
		||||
 | 
			
		||||
    def handleNotification(self, handle, data):
 | 
			
		||||
        """Handle Callback from a Bluetooth (GATT) request."""
 | 
			
		||||
        _LOGGER.debug(
 | 
			
		||||
            "Got notification from %s: %s", handle, codecs.encode(data, "hex")
 | 
			
		||||
        )
 | 
			
		||||
        if handle in self._callbacks:
 | 
			
		||||
            self._callbacks[handle](data)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def mac(self):
 | 
			
		||||
        """Return the MAC address of the connected device."""
 | 
			
		||||
        return self._mac
 | 
			
		||||
 | 
			
		||||
    def set_callback(self, handle, function):
 | 
			
		||||
        """Set the callback for a Notification handle. It will be called with the parameter data, which is binary."""
 | 
			
		||||
        self._callbacks[handle] = function
 | 
			
		||||
 | 
			
		||||
    def make_request(self, handle, value, timeout=DEFAULT_TIMEOUT, with_response=True):
 | 
			
		||||
        """Write a GATT Command without callback - not utf-8."""
 | 
			
		||||
        try:
 | 
			
		||||
            with self:
 | 
			
		||||
                _LOGGER.debug(
 | 
			
		||||
                    "Writing %s to %s with with_response=%s",
 | 
			
		||||
                    codecs.encode(value, "hex"),
 | 
			
		||||
                    handle,
 | 
			
		||||
                    with_response,
 | 
			
		||||
                )
 | 
			
		||||
                self._conn.writeCharacteristic(
 | 
			
		||||
                    handle, value, withResponse=with_response
 | 
			
		||||
                )
 | 
			
		||||
                if timeout:
 | 
			
		||||
                    _LOGGER.debug("Waiting for notifications for %s", timeout)
 | 
			
		||||
                    self._conn.waitForNotifications(timeout)
 | 
			
		||||
        except btle.BTLEException as ex:
 | 
			
		||||
            _LOGGER.debug("Got exception from bluepy while making a request: %s", ex)
 | 
			
		||||
            raise BackendException("Exception on write using bluepy") from ex
 | 
			
		||||
							
								
								
									
										491
									
								
								deps/lib/python3.10/site-packages/eq3bt/eq3btsmart.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										491
									
								
								deps/lib/python3.10/site-packages/eq3bt/eq3btsmart.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,491 @@
 | 
			
		||||
"""
 | 
			
		||||
Support for eq3 Bluetooth Smart thermostats.
 | 
			
		||||
 | 
			
		||||
All temperatures in Celsius.
 | 
			
		||||
 | 
			
		||||
To get the current state, update() has to be called for powersaving reasons.
 | 
			
		||||
Schedule needs to be requested with query_schedule() before accessing for similar reasons.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import codecs
 | 
			
		||||
import logging
 | 
			
		||||
import struct
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from enum import IntEnum
 | 
			
		||||
 | 
			
		||||
from construct import Byte
 | 
			
		||||
 | 
			
		||||
from .structures import AwayDataAdapter, DeviceId, Schedule, Status
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
PROP_WRITE_HANDLE = 0x411
 | 
			
		||||
PROP_NTFY_HANDLE = 0x421
 | 
			
		||||
 | 
			
		||||
PROP_ID_QUERY = 0
 | 
			
		||||
PROP_ID_RETURN = 1
 | 
			
		||||
PROP_INFO_QUERY = 3
 | 
			
		||||
PROP_INFO_RETURN = 2
 | 
			
		||||
PROP_COMFORT_ECO_CONFIG = 0x11
 | 
			
		||||
PROP_OFFSET = 0x13
 | 
			
		||||
PROP_WINDOW_OPEN_CONFIG = 0x14
 | 
			
		||||
PROP_SCHEDULE_QUERY = 0x20
 | 
			
		||||
PROP_SCHEDULE_RETURN = 0x21
 | 
			
		||||
 | 
			
		||||
PROP_MODE_WRITE = 0x40
 | 
			
		||||
PROP_TEMPERATURE_WRITE = 0x41
 | 
			
		||||
PROP_COMFORT = 0x43
 | 
			
		||||
PROP_ECO = 0x44
 | 
			
		||||
PROP_BOOST = 0x45
 | 
			
		||||
PROP_LOCK = 0x80
 | 
			
		||||
 | 
			
		||||
EQ3BT_AWAY_TEMP = 12.0
 | 
			
		||||
EQ3BT_MIN_TEMP = 5.0
 | 
			
		||||
EQ3BT_MAX_TEMP = 29.5
 | 
			
		||||
EQ3BT_OFF_TEMP = 4.5
 | 
			
		||||
EQ3BT_ON_TEMP = 30.0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Mode(IntEnum):
 | 
			
		||||
    """Thermostat modes."""
 | 
			
		||||
 | 
			
		||||
    Unknown = -1
 | 
			
		||||
    Closed = 0
 | 
			
		||||
    Open = 1
 | 
			
		||||
    Auto = 2
 | 
			
		||||
    Manual = 3
 | 
			
		||||
    Away = 4
 | 
			
		||||
    Boost = 5
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MODE_NOT_TEMP = [Mode.Unknown, Mode.Closed, Mode.Open]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TemperatureException(Exception):
 | 
			
		||||
    """Temperature out of range error."""
 | 
			
		||||
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# pylint: disable=too-many-instance-attributes
 | 
			
		||||
class Thermostat:
 | 
			
		||||
    """Representation of a EQ3 Bluetooth Smart thermostat."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, _mac, _iface=None, connection_cls=None):
 | 
			
		||||
        """Initialize the thermostat."""
 | 
			
		||||
 | 
			
		||||
        self._target_temperature = Mode.Unknown
 | 
			
		||||
        self._mode = Mode.Unknown
 | 
			
		||||
        self._valve_state = Mode.Unknown
 | 
			
		||||
        self._raw_mode = None
 | 
			
		||||
 | 
			
		||||
        self._schedule = {}
 | 
			
		||||
 | 
			
		||||
        self._window_open_temperature = None
 | 
			
		||||
        self._window_open_time = None
 | 
			
		||||
        self._comfort_temperature = None
 | 
			
		||||
        self._eco_temperature = None
 | 
			
		||||
        self._temperature_offset = None
 | 
			
		||||
 | 
			
		||||
        self._away_temp = EQ3BT_AWAY_TEMP
 | 
			
		||||
        self._away_duration = timedelta(days=30)
 | 
			
		||||
        self._away_end = None
 | 
			
		||||
 | 
			
		||||
        self._firmware_version = None
 | 
			
		||||
        self._device_serial = None
 | 
			
		||||
 | 
			
		||||
        if connection_cls is None:
 | 
			
		||||
            from .bleakconnection import BleakConnection as connection_cls
 | 
			
		||||
        self._conn = connection_cls(_mac, _iface)
 | 
			
		||||
        self._conn.set_callback(PROP_NTFY_HANDLE, self.handle_notification)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        away_end = "no"
 | 
			
		||||
        if self.away_end:
 | 
			
		||||
            away_end = "end: %s" % self._away_end
 | 
			
		||||
 | 
			
		||||
        return "[{}] Target {} (mode: {}, away: {})".format(
 | 
			
		||||
            self._conn.mac, self.target_temperature, self.mode_readable, away_end
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def _verify_temperature(self, temp):
 | 
			
		||||
        """Verifies that the temperature is valid.
 | 
			
		||||
        :raises TemperatureException: On invalid temperature.
 | 
			
		||||
        """
 | 
			
		||||
        if temp < self.min_temp or temp > self.max_temp:
 | 
			
		||||
            raise TemperatureException(
 | 
			
		||||
                "Temperature {} out of range [{}, {}]".format(
 | 
			
		||||
                    temp, self.min_temp, self.max_temp
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    def parse_schedule(self, data):
 | 
			
		||||
        """Parses the device sent schedule."""
 | 
			
		||||
        sched = Schedule.parse(data)
 | 
			
		||||
        _LOGGER.debug("Got schedule data for day '%s'", sched.day)
 | 
			
		||||
 | 
			
		||||
        return sched
 | 
			
		||||
 | 
			
		||||
    def handle_notification(self, data):
 | 
			
		||||
        """Handle Callback from a Bluetooth (GATT) request."""
 | 
			
		||||
        _LOGGER.debug("Received notification from the device..")
 | 
			
		||||
 | 
			
		||||
        if data[0] == PROP_INFO_RETURN and data[1] == 1:
 | 
			
		||||
            _LOGGER.debug("Got status: %s" % codecs.encode(data, "hex"))
 | 
			
		||||
            status = Status.parse(data)
 | 
			
		||||
            _LOGGER.debug("Parsed status: %s", status)
 | 
			
		||||
 | 
			
		||||
            self._raw_mode = status.mode
 | 
			
		||||
            self._valve_state = status.valve
 | 
			
		||||
            self._target_temperature = status.target_temp
 | 
			
		||||
 | 
			
		||||
            if status.mode.BOOST:
 | 
			
		||||
                self._mode = Mode.Boost
 | 
			
		||||
            elif status.mode.AWAY:
 | 
			
		||||
                self._mode = Mode.Away
 | 
			
		||||
                self._away_end = status.away
 | 
			
		||||
            elif status.mode.MANUAL:
 | 
			
		||||
                if status.target_temp == EQ3BT_OFF_TEMP:
 | 
			
		||||
                    self._mode = Mode.Closed
 | 
			
		||||
                elif status.target_temp == EQ3BT_ON_TEMP:
 | 
			
		||||
                    self._mode = Mode.Open
 | 
			
		||||
                else:
 | 
			
		||||
                    self._mode = Mode.Manual
 | 
			
		||||
            else:
 | 
			
		||||
                self._mode = Mode.Auto
 | 
			
		||||
 | 
			
		||||
            presets = status.presets
 | 
			
		||||
            if presets:
 | 
			
		||||
                self._window_open_temperature = presets.window_open_temp
 | 
			
		||||
                self._window_open_time = presets.window_open_time
 | 
			
		||||
                self._comfort_temperature = presets.comfort_temp
 | 
			
		||||
                self._eco_temperature = presets.eco_temp
 | 
			
		||||
                self._temperature_offset = presets.offset
 | 
			
		||||
            else:
 | 
			
		||||
                self._window_open_temperature = None
 | 
			
		||||
                self._window_open_time = None
 | 
			
		||||
                self._comfort_temperature = None
 | 
			
		||||
                self._eco_temperature = None
 | 
			
		||||
                self._temperature_offset = None
 | 
			
		||||
 | 
			
		||||
            _LOGGER.debug("Valve state:      %s", self._valve_state)
 | 
			
		||||
            _LOGGER.debug("Mode:             %s", self.mode_readable)
 | 
			
		||||
            _LOGGER.debug("Target temp:      %s", self._target_temperature)
 | 
			
		||||
            _LOGGER.debug("Away end:         %s", self._away_end)
 | 
			
		||||
            _LOGGER.debug("Window open temp: %s", self._window_open_temperature)
 | 
			
		||||
            _LOGGER.debug("Window open time: %s", self._window_open_time)
 | 
			
		||||
            _LOGGER.debug("Comfort temp:     %s", self._comfort_temperature)
 | 
			
		||||
            _LOGGER.debug("Eco temp:         %s", self._eco_temperature)
 | 
			
		||||
            _LOGGER.debug("Temp offset:      %s", self._temperature_offset)
 | 
			
		||||
 | 
			
		||||
        elif data[0] == PROP_SCHEDULE_RETURN:
 | 
			
		||||
            parsed = self.parse_schedule(data)
 | 
			
		||||
            self._schedule[parsed.day] = parsed
 | 
			
		||||
 | 
			
		||||
        elif data[0] == PROP_ID_RETURN:
 | 
			
		||||
            parsed = DeviceId.parse(data)
 | 
			
		||||
            _LOGGER.debug("Parsed device data: %s", parsed)
 | 
			
		||||
            self._firmware_version = parsed.version
 | 
			
		||||
            self._device_serial = parsed.serial
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            _LOGGER.debug(
 | 
			
		||||
                "Unknown notification %s (%s)", data[0], codecs.encode(data, "hex")
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    def query_id(self):
 | 
			
		||||
        """Query device identification information, e.g. the serial number."""
 | 
			
		||||
        _LOGGER.debug("Querying id..")
 | 
			
		||||
        value = struct.pack("B", PROP_ID_QUERY)
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    def update(self):
 | 
			
		||||
        """Update the data from the thermostat. Always sets the current time."""
 | 
			
		||||
        _LOGGER.debug("Querying the device..")
 | 
			
		||||
        time = datetime.now()
 | 
			
		||||
        value = struct.pack(
 | 
			
		||||
            "BBBBBBB",
 | 
			
		||||
            PROP_INFO_QUERY,
 | 
			
		||||
            time.year % 100,
 | 
			
		||||
            time.month,
 | 
			
		||||
            time.day,
 | 
			
		||||
            time.hour,
 | 
			
		||||
            time.minute,
 | 
			
		||||
            time.second,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    def query_schedule(self, day):
 | 
			
		||||
        _LOGGER.debug("Querying schedule..")
 | 
			
		||||
 | 
			
		||||
        if day < 0 or day > 6:
 | 
			
		||||
            _LOGGER.error("Invalid day: %s", day)
 | 
			
		||||
 | 
			
		||||
        value = struct.pack("BB", PROP_SCHEDULE_QUERY, day)
 | 
			
		||||
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def schedule(self):
 | 
			
		||||
        """Returns previously fetched schedule.
 | 
			
		||||
        :return: Schedule structure or None if not fetched.
 | 
			
		||||
        """
 | 
			
		||||
        return self._schedule
 | 
			
		||||
 | 
			
		||||
    def set_schedule(self, data):
 | 
			
		||||
        """Sets the schedule for the given day."""
 | 
			
		||||
        value = Schedule.build(data)
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def target_temperature(self):
 | 
			
		||||
        """Return the temperature we try to reach."""
 | 
			
		||||
        return self._target_temperature
 | 
			
		||||
 | 
			
		||||
    @target_temperature.setter
 | 
			
		||||
    def target_temperature(self, temperature):
 | 
			
		||||
        """Set new target temperature."""
 | 
			
		||||
        dev_temp = int(temperature * 2)
 | 
			
		||||
        if temperature == EQ3BT_OFF_TEMP or temperature == EQ3BT_ON_TEMP:
 | 
			
		||||
            dev_temp |= 0x40
 | 
			
		||||
            value = struct.pack("BB", PROP_MODE_WRITE, dev_temp)
 | 
			
		||||
        else:
 | 
			
		||||
            self._verify_temperature(temperature)
 | 
			
		||||
            value = struct.pack("BB", PROP_TEMPERATURE_WRITE, dev_temp)
 | 
			
		||||
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def mode(self):
 | 
			
		||||
        """Return the current operation mode"""
 | 
			
		||||
        return self._mode
 | 
			
		||||
 | 
			
		||||
    @mode.setter
 | 
			
		||||
    def mode(self, mode):
 | 
			
		||||
        """Set the operation mode."""
 | 
			
		||||
        _LOGGER.debug("Setting new mode: %s", mode)
 | 
			
		||||
 | 
			
		||||
        if self.mode == Mode.Boost and mode != Mode.Boost:
 | 
			
		||||
            self.boost = False
 | 
			
		||||
 | 
			
		||||
        if mode == Mode.Boost:
 | 
			
		||||
            self.boost = True
 | 
			
		||||
            return
 | 
			
		||||
        elif mode == Mode.Away:
 | 
			
		||||
            end = datetime.now() + self._away_duration
 | 
			
		||||
            return self.set_away(end, self._away_temp)
 | 
			
		||||
        elif mode == Mode.Closed:
 | 
			
		||||
            return self.set_mode(0x40 | int(EQ3BT_OFF_TEMP * 2))
 | 
			
		||||
        elif mode == Mode.Open:
 | 
			
		||||
            return self.set_mode(0x40 | int(EQ3BT_ON_TEMP * 2))
 | 
			
		||||
 | 
			
		||||
        if mode == Mode.Manual:
 | 
			
		||||
            temperature = max(
 | 
			
		||||
                min(self._target_temperature, self.max_temp), self.min_temp
 | 
			
		||||
            )
 | 
			
		||||
            return self.set_mode(0x40 | int(temperature * 2))
 | 
			
		||||
        else:
 | 
			
		||||
            return self.set_mode(0)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def away_end(self):
 | 
			
		||||
        return self._away_end
 | 
			
		||||
 | 
			
		||||
    def set_away(self, away_end=None, temperature=EQ3BT_AWAY_TEMP):
 | 
			
		||||
        """Sets away mode with target temperature.
 | 
			
		||||
        When called without parameters disables away mode."""
 | 
			
		||||
        if not away_end:
 | 
			
		||||
            _LOGGER.debug("Disabling away, going to auto mode.")
 | 
			
		||||
            return self.set_mode(0x00)
 | 
			
		||||
 | 
			
		||||
        _LOGGER.debug("Setting away until %s, temp %s", away_end, temperature)
 | 
			
		||||
        adapter = AwayDataAdapter(Byte[4])
 | 
			
		||||
        packed = adapter.build(away_end)
 | 
			
		||||
 | 
			
		||||
        self.set_mode(0x80 | int(temperature * 2), packed)
 | 
			
		||||
 | 
			
		||||
    def set_mode(self, mode, payload=None):
 | 
			
		||||
        value = struct.pack("BB", PROP_MODE_WRITE, mode)
 | 
			
		||||
        if payload:
 | 
			
		||||
            value += payload
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def mode_readable(self):
 | 
			
		||||
        """Return a readable representation of the mode.."""
 | 
			
		||||
        ret = ""
 | 
			
		||||
        mode = self._raw_mode
 | 
			
		||||
 | 
			
		||||
        if mode.MANUAL:
 | 
			
		||||
            ret = "manual"
 | 
			
		||||
            if self.target_temperature < self.min_temp:
 | 
			
		||||
                ret += " off"
 | 
			
		||||
            elif self.target_temperature >= self.max_temp:
 | 
			
		||||
                ret += " on"
 | 
			
		||||
            else:
 | 
			
		||||
                ret += " (%sC)" % self.target_temperature
 | 
			
		||||
        else:
 | 
			
		||||
            ret = "auto"
 | 
			
		||||
 | 
			
		||||
        if mode.AWAY:
 | 
			
		||||
            ret += " holiday"
 | 
			
		||||
        if mode.BOOST:
 | 
			
		||||
            ret += " boost"
 | 
			
		||||
        if mode.DST:
 | 
			
		||||
            ret += " dst"
 | 
			
		||||
        if mode.WINDOW:
 | 
			
		||||
            ret += " window"
 | 
			
		||||
        if mode.LOCKED:
 | 
			
		||||
            ret += " locked"
 | 
			
		||||
        if mode.LOW_BATTERY:
 | 
			
		||||
            ret += " low battery"
 | 
			
		||||
 | 
			
		||||
        return ret
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def boost(self):
 | 
			
		||||
        """Returns True if the thermostat is in boost mode."""
 | 
			
		||||
        return self.mode == Mode.Boost
 | 
			
		||||
 | 
			
		||||
    @boost.setter
 | 
			
		||||
    def boost(self, boost):
 | 
			
		||||
        """Sets boost mode."""
 | 
			
		||||
        _LOGGER.debug("Setting boost mode: %s", boost)
 | 
			
		||||
        value = struct.pack("BB", PROP_BOOST, bool(boost))
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def valve_state(self):
 | 
			
		||||
        """Returns the valve state. Probably reported as percent open."""
 | 
			
		||||
        return self._valve_state
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def window_open(self):
 | 
			
		||||
        """Returns True if the thermostat reports a open window
 | 
			
		||||
        (detected by sudden drop of temperature)"""
 | 
			
		||||
        return self._raw_mode and self._raw_mode.WINDOW
 | 
			
		||||
 | 
			
		||||
    def window_open_config(self, temperature, duration):
 | 
			
		||||
        """Configures the window open behavior. The duration is specified in
 | 
			
		||||
        5 minute increments."""
 | 
			
		||||
        _LOGGER.debug(
 | 
			
		||||
            "Window open config, temperature: %s duration: %s", temperature, duration
 | 
			
		||||
        )
 | 
			
		||||
        self._verify_temperature(temperature)
 | 
			
		||||
        if duration.seconds < 0 and duration.seconds > 3600:
 | 
			
		||||
            raise ValueError
 | 
			
		||||
 | 
			
		||||
        value = struct.pack(
 | 
			
		||||
            "BBB",
 | 
			
		||||
            PROP_WINDOW_OPEN_CONFIG,
 | 
			
		||||
            int(temperature * 2),
 | 
			
		||||
            int(duration.seconds / 300),
 | 
			
		||||
        )
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def window_open_temperature(self):
 | 
			
		||||
        """The temperature to set when an open window is detected."""
 | 
			
		||||
        return self._window_open_temperature
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def window_open_time(self):
 | 
			
		||||
        """Timeout to reset the thermostat after an open window is detected."""
 | 
			
		||||
        return self._window_open_time
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def locked(self):
 | 
			
		||||
        """Returns True if the thermostat is locked."""
 | 
			
		||||
        return self._raw_mode and self._raw_mode.LOCKED
 | 
			
		||||
 | 
			
		||||
    @locked.setter
 | 
			
		||||
    def locked(self, lock):
 | 
			
		||||
        """Locks or unlocks the thermostat."""
 | 
			
		||||
        _LOGGER.debug("Setting the lock: %s", lock)
 | 
			
		||||
        value = struct.pack("BB", PROP_LOCK, bool(lock))
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def low_battery(self):
 | 
			
		||||
        """Returns True if the thermostat reports a low battery."""
 | 
			
		||||
        return self._raw_mode and self._raw_mode.LOW_BATTERY
 | 
			
		||||
 | 
			
		||||
    def temperature_presets(self, comfort, eco):
 | 
			
		||||
        """Set the thermostats preset temperatures comfort (sun) and
 | 
			
		||||
        eco (moon)."""
 | 
			
		||||
        _LOGGER.debug("Setting temperature presets, comfort: %s eco: %s", comfort, eco)
 | 
			
		||||
        self._verify_temperature(comfort)
 | 
			
		||||
        self._verify_temperature(eco)
 | 
			
		||||
        value = struct.pack(
 | 
			
		||||
            "BBB", PROP_COMFORT_ECO_CONFIG, int(comfort * 2), int(eco * 2)
 | 
			
		||||
        )
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def comfort_temperature(self):
 | 
			
		||||
        """Returns the comfort temperature preset of the thermostat."""
 | 
			
		||||
        return self._comfort_temperature
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def eco_temperature(self):
 | 
			
		||||
        """Returns the eco temperature preset of the thermostat."""
 | 
			
		||||
        return self._eco_temperature
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def temperature_offset(self):
 | 
			
		||||
        """Returns the thermostat's temperature offset."""
 | 
			
		||||
        return self._temperature_offset
 | 
			
		||||
 | 
			
		||||
    @temperature_offset.setter
 | 
			
		||||
    def temperature_offset(self, offset):
 | 
			
		||||
        """Sets the thermostat's temperature offset."""
 | 
			
		||||
        _LOGGER.debug("Setting offset: %s", offset)
 | 
			
		||||
        # [-3,5 .. 0  .. 3,5 ]
 | 
			
		||||
        # [00   .. 07 .. 0e ]
 | 
			
		||||
        if offset < -3.5 or offset > 3.5:
 | 
			
		||||
            raise TemperatureException("Invalid value: %s" % offset)
 | 
			
		||||
 | 
			
		||||
        current = -3.5
 | 
			
		||||
        values = {}
 | 
			
		||||
        for i in range(15):
 | 
			
		||||
            values[current] = i
 | 
			
		||||
            current += 0.5
 | 
			
		||||
 | 
			
		||||
        value = struct.pack("BB", PROP_OFFSET, values[offset])
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    def activate_comfort(self):
 | 
			
		||||
        """Activates the comfort temperature."""
 | 
			
		||||
        value = struct.pack("B", PROP_COMFORT)
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    def activate_eco(self):
 | 
			
		||||
        """Activates the comfort temperature."""
 | 
			
		||||
        value = struct.pack("B", PROP_ECO)
 | 
			
		||||
        self._conn.make_request(PROP_WRITE_HANDLE, value)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def min_temp(self):
 | 
			
		||||
        """Return the minimum temperature."""
 | 
			
		||||
        return EQ3BT_MIN_TEMP
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def max_temp(self):
 | 
			
		||||
        """Return the maximum temperature."""
 | 
			
		||||
        return EQ3BT_MAX_TEMP
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def firmware_version(self):
 | 
			
		||||
        """Return the firmware version."""
 | 
			
		||||
        return self._firmware_version
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def device_serial(self):
 | 
			
		||||
        """Return the device serial number."""
 | 
			
		||||
        return self._device_serial
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def mac(self):
 | 
			
		||||
        """Return the mac address."""
 | 
			
		||||
        return self._conn.mac
 | 
			
		||||
							
								
								
									
										212
									
								
								deps/lib/python3.10/site-packages/eq3bt/eq3cli.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								deps/lib/python3.10/site-packages/eq3bt/eq3cli.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,212 @@
 | 
			
		||||
""" Cli tool for testing connectivity with EQ3 smart thermostats. """
 | 
			
		||||
import logging
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
import click
 | 
			
		||||
 | 
			
		||||
from eq3bt import Thermostat
 | 
			
		||||
 | 
			
		||||
pass_dev = click.make_pass_decorator(Thermostat)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_mac(ctx, param, mac):
 | 
			
		||||
    if re.match("^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$", mac) is None:
 | 
			
		||||
        raise click.BadParameter(mac + " is no valid mac address")
 | 
			
		||||
    return mac
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@click.group(invoke_without_command=True)
 | 
			
		||||
@click.option("--mac", envvar="EQ3_MAC", required=True, callback=validate_mac)
 | 
			
		||||
@click.option("--interface", default=None)
 | 
			
		||||
@click.option("--debug/--normal", default=False)
 | 
			
		||||
@click.option(
 | 
			
		||||
    "--backend", type=click.Choice(["bleak", "bluepy", "gattlib"]), default="bleak"
 | 
			
		||||
)
 | 
			
		||||
@click.pass_context
 | 
			
		||||
def cli(ctx, mac, interface, debug, backend):
 | 
			
		||||
    """Tool to query and modify the state of EQ3 BT smart thermostat."""
 | 
			
		||||
    if debug:
 | 
			
		||||
        logging.basicConfig(level=logging.DEBUG)
 | 
			
		||||
    else:
 | 
			
		||||
        logging.basicConfig(level=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    if backend == "bluepy":
 | 
			
		||||
        from .connection import BTLEConnection
 | 
			
		||||
 | 
			
		||||
        connection_cls = BTLEConnection
 | 
			
		||||
    elif backend == "gattlib":
 | 
			
		||||
        from .gattlibconnection import BTLEConnection
 | 
			
		||||
 | 
			
		||||
        connection_cls = BTLEConnection
 | 
			
		||||
    else:
 | 
			
		||||
        from .bleakconnection import BleakConnection
 | 
			
		||||
 | 
			
		||||
        connection_cls = BleakConnection
 | 
			
		||||
 | 
			
		||||
    thermostat = Thermostat(mac, interface, connection_cls)
 | 
			
		||||
    thermostat.update()
 | 
			
		||||
    ctx.obj = thermostat
 | 
			
		||||
 | 
			
		||||
    if ctx.invoked_subcommand is None:
 | 
			
		||||
        ctx.invoke(state)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@click.option("--target", type=float, required=False)
 | 
			
		||||
@pass_dev
 | 
			
		||||
def temp(dev, target):
 | 
			
		||||
    """Gets or sets the target temperature."""
 | 
			
		||||
    click.echo("Current target temp: %s" % dev.target_temperature)
 | 
			
		||||
    if target:
 | 
			
		||||
        click.echo("Setting target temp: %s" % target)
 | 
			
		||||
        dev.target_temperature = target
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@click.option("--target", type=int, required=False)
 | 
			
		||||
@pass_dev
 | 
			
		||||
def mode(dev, target):
 | 
			
		||||
    """Gets or sets the active mode."""
 | 
			
		||||
    click.echo("Current mode: %s" % dev.mode_readable)
 | 
			
		||||
    if target:
 | 
			
		||||
        click.echo("Setting mode: %s" % target)
 | 
			
		||||
        dev.mode = target
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@click.option("--target", type=bool, required=False)
 | 
			
		||||
@pass_dev
 | 
			
		||||
def boost(dev, target):
 | 
			
		||||
    """Gets or sets the boost mode."""
 | 
			
		||||
    click.echo("Boost: %s" % dev.boost)
 | 
			
		||||
    if target is not None:
 | 
			
		||||
        click.echo("Setting boost: %s" % target)
 | 
			
		||||
        dev.boost = target
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@pass_dev
 | 
			
		||||
def valve_state(dev):
 | 
			
		||||
    """Gets the state of the valve."""
 | 
			
		||||
    click.echo("Valve: %s" % dev.valve_state)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@click.option("--target", type=bool, required=False)
 | 
			
		||||
@pass_dev
 | 
			
		||||
def locked(dev, target):
 | 
			
		||||
    """Gets or sets the lock."""
 | 
			
		||||
    click.echo("Locked: %s" % dev.locked)
 | 
			
		||||
    if target is not None:
 | 
			
		||||
        click.echo("Setting lock: %s" % target)
 | 
			
		||||
        dev.locked = target
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@pass_dev
 | 
			
		||||
def low_battery(dev):
 | 
			
		||||
    """Gets the low battery status."""
 | 
			
		||||
    click.echo("Batter low: %s" % dev.low_battery)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@click.option("--temp", type=float, required=False)
 | 
			
		||||
@click.option("--duration", type=float, required=False)
 | 
			
		||||
@pass_dev
 | 
			
		||||
def window_open(dev, temp, duration):
 | 
			
		||||
    """Gets and sets the window open settings."""
 | 
			
		||||
    click.echo("Window open: %s" % dev.window_open)
 | 
			
		||||
    if dev.window_open_temperature is not None:
 | 
			
		||||
        click.echo("Window open temp: %s" % dev.window_open_temperature)
 | 
			
		||||
    if dev.window_open_time is not None:
 | 
			
		||||
        click.echo("Window open time: %s" % dev.window_open_time)
 | 
			
		||||
    if temp and duration:
 | 
			
		||||
        click.echo(f"Setting window open conf, temp: {temp} duration: {duration}")
 | 
			
		||||
        dev.window_open_config(temp, duration)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@click.option("--comfort", type=float, required=False)
 | 
			
		||||
@click.option("--eco", type=float, required=False)
 | 
			
		||||
@pass_dev
 | 
			
		||||
def presets(dev, comfort, eco):
 | 
			
		||||
    """Sets the preset temperatures for auto mode."""
 | 
			
		||||
    if dev.comfort_temperature is not None:
 | 
			
		||||
        click.echo("Current comfort temp: %s" % dev.comfort_temperature)
 | 
			
		||||
    if dev.eco_temperature is not None:
 | 
			
		||||
        click.echo("Current eco temp: %s" % dev.eco_temperature)
 | 
			
		||||
    if comfort and eco:
 | 
			
		||||
        click.echo(f"Setting presets: comfort {comfort}, eco {eco}")
 | 
			
		||||
        dev.temperature_presets(comfort, eco)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@pass_dev
 | 
			
		||||
def schedule(dev):
 | 
			
		||||
    """Gets the schedule from the thermostat."""
 | 
			
		||||
    # TODO: expose setting the schedule somehow?
 | 
			
		||||
    for d in range(7):
 | 
			
		||||
        dev.query_schedule(d)
 | 
			
		||||
    for day in dev.schedule.values():
 | 
			
		||||
        click.echo(f"Day {day.day}, base temp: {day.base_temp}")
 | 
			
		||||
        current_hour = day.next_change_at
 | 
			
		||||
        for hour in day.hours:
 | 
			
		||||
            if current_hour == 0:
 | 
			
		||||
                continue
 | 
			
		||||
            click.echo(f"\t[{current_hour}-{hour.next_change_at}] {hour.target_temp}")
 | 
			
		||||
            current_hour = hour.next_change_at
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@click.argument("offset", type=float, required=False)
 | 
			
		||||
@pass_dev
 | 
			
		||||
def offset(dev, offset):
 | 
			
		||||
    """Sets the temperature offset [-3,5 3,5]"""
 | 
			
		||||
    if dev.temperature_offset is not None:
 | 
			
		||||
        click.echo("Current temp offset: %s" % dev.temperature_offset)
 | 
			
		||||
    if offset is not None:
 | 
			
		||||
        click.echo("Setting the offset to %s" % offset)
 | 
			
		||||
        dev.temperature_offset = offset
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@click.argument("away_end", type=click.DateTime(), default=None, required=False)
 | 
			
		||||
@click.argument("temperature", type=float, default=None, required=False)
 | 
			
		||||
@pass_dev
 | 
			
		||||
def away(dev, away_end, temperature):
 | 
			
		||||
    """Enables or disables the away mode."""
 | 
			
		||||
    if away_end:
 | 
			
		||||
        click.echo(f"Setting away until {away_end}, temperature: {temperature}")
 | 
			
		||||
    else:
 | 
			
		||||
        click.echo("Disabling away mode")
 | 
			
		||||
    dev.set_away(away_end, temperature)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@pass_dev
 | 
			
		||||
def device(dev):
 | 
			
		||||
    """Displays basic device information."""
 | 
			
		||||
    dev.query_id()
 | 
			
		||||
    click.echo("Firmware version: %s" % dev.firmware_version)
 | 
			
		||||
    click.echo("Device serial:    %s" % dev.device_serial)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cli.command()
 | 
			
		||||
@click.pass_context
 | 
			
		||||
def state(ctx):
 | 
			
		||||
    """Prints out all available information."""
 | 
			
		||||
    dev = ctx.obj
 | 
			
		||||
    click.echo(dev)
 | 
			
		||||
    ctx.forward(locked)
 | 
			
		||||
    ctx.forward(low_battery)
 | 
			
		||||
    ctx.forward(window_open)
 | 
			
		||||
    ctx.forward(boost)
 | 
			
		||||
    ctx.forward(temp)
 | 
			
		||||
    ctx.forward(presets)
 | 
			
		||||
    ctx.forward(offset)
 | 
			
		||||
    ctx.forward(mode)
 | 
			
		||||
    ctx.forward(valve_state)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    cli()
 | 
			
		||||
							
								
								
									
										99
									
								
								deps/lib/python3.10/site-packages/eq3bt/gattlibconnection.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								deps/lib/python3.10/site-packages/eq3bt/gattlibconnection.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
			
		||||
"""
 | 
			
		||||
A simple adapter to gattlib.
 | 
			
		||||
Handles Connection duties (reconnecting etc.) transparently.
 | 
			
		||||
"""
 | 
			
		||||
import codecs
 | 
			
		||||
import logging
 | 
			
		||||
import threading
 | 
			
		||||
 | 
			
		||||
import gattlib
 | 
			
		||||
 | 
			
		||||
from . import BackendException
 | 
			
		||||
 | 
			
		||||
DEFAULT_TIMEOUT = 1
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BTLEConnection:
 | 
			
		||||
    """Representation of a BTLE Connection."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, mac, iface):
 | 
			
		||||
        """Initialize the connection."""
 | 
			
		||||
 | 
			
		||||
        self._conn = None
 | 
			
		||||
        self._mac = mac
 | 
			
		||||
        self._iface = iface
 | 
			
		||||
        self._callbacks = {}
 | 
			
		||||
        self._notifyevent = None
 | 
			
		||||
 | 
			
		||||
    def __enter__(self):
 | 
			
		||||
        """
 | 
			
		||||
        Context manager __enter__ for connecting the device
 | 
			
		||||
        :rtype: BTLEConnection
 | 
			
		||||
        :return:
 | 
			
		||||
        """
 | 
			
		||||
        _LOGGER.debug("Trying to connect to %s", self._mac)
 | 
			
		||||
        if self._iface is None:
 | 
			
		||||
            self._conn = gattlib.GATTRequester(self._mac, False)
 | 
			
		||||
        else:
 | 
			
		||||
            self._conn = gattlib.GATTRequester(self._mac, False, self._iface)
 | 
			
		||||
        self._conn.on_notification = self.on_notification
 | 
			
		||||
        try:
 | 
			
		||||
            self._conn.connect()
 | 
			
		||||
        except gattlib.BTBaseException as ex:
 | 
			
		||||
            _LOGGER.debug(
 | 
			
		||||
                "Unable to connect to the device %s, retrying: %s", self._mac, ex
 | 
			
		||||
            )
 | 
			
		||||
            try:
 | 
			
		||||
                self._conn.connect()
 | 
			
		||||
            except Exception as ex2:
 | 
			
		||||
                _LOGGER.debug("Second connection try to %s failed: %s", self._mac, ex2)
 | 
			
		||||
                raise BackendException(
 | 
			
		||||
                    "unable to connect to device using gattlib"
 | 
			
		||||
                ) from ex2
 | 
			
		||||
 | 
			
		||||
        _LOGGER.debug("Connected to %s", self._mac)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def __exit__(self, exc_type, exc_val, exc_tb):
 | 
			
		||||
        if self._conn:
 | 
			
		||||
            self._conn.disconnect()
 | 
			
		||||
            self._conn = None
 | 
			
		||||
 | 
			
		||||
    def on_notification(self, handle, data):
 | 
			
		||||
        """Handle Callback from a Bluetooth (GATT) request."""
 | 
			
		||||
        _LOGGER.debug(
 | 
			
		||||
            "Got notification from %s: %s", handle, codecs.encode(data, "hex")
 | 
			
		||||
        )
 | 
			
		||||
        if handle in self._callbacks:
 | 
			
		||||
            self._callbacks[handle](data[3:])
 | 
			
		||||
        if self._notifyevent:
 | 
			
		||||
            self._notifyevent.set()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def mac(self):
 | 
			
		||||
        """Return the MAC address of the connected device."""
 | 
			
		||||
        return self._mac
 | 
			
		||||
 | 
			
		||||
    def set_callback(self, handle, function):
 | 
			
		||||
        """Set the callback for a Notification handle. It will be called with the parameter data, which is binary."""
 | 
			
		||||
        self._callbacks[handle] = function
 | 
			
		||||
 | 
			
		||||
    def make_request(self, handle, value, timeout=DEFAULT_TIMEOUT, with_response=True):
 | 
			
		||||
        """Write a GATT Command without callback - not utf-8."""
 | 
			
		||||
        try:
 | 
			
		||||
            with self:
 | 
			
		||||
                _LOGGER.debug(
 | 
			
		||||
                    "Writing %s to %s",
 | 
			
		||||
                    codecs.encode(value, "hex"),
 | 
			
		||||
                    handle,
 | 
			
		||||
                )
 | 
			
		||||
                self._notifyevent = threading.Event()
 | 
			
		||||
                self._conn.write_by_handle(handle, value)
 | 
			
		||||
                if timeout:
 | 
			
		||||
                    _LOGGER.debug("Waiting for notifications for %s", timeout)
 | 
			
		||||
                    self._notifyevent.wait(timeout)
 | 
			
		||||
        except gattlib.BTBaseException as ex:
 | 
			
		||||
            _LOGGER.debug("Got exception from gattlib while making a request: %s", ex)
 | 
			
		||||
            raise BackendException("Exception on write using gattlib") from ex
 | 
			
		||||
							
								
								
									
										174
									
								
								deps/lib/python3.10/site-packages/eq3bt/structures.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								deps/lib/python3.10/site-packages/eq3bt/structures.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,174 @@
 | 
			
		||||
""" Contains construct adapters and structures. """
 | 
			
		||||
from datetime import datetime, time, timedelta
 | 
			
		||||
 | 
			
		||||
from construct import (
 | 
			
		||||
    Adapter,
 | 
			
		||||
    Bytes,
 | 
			
		||||
    Const,
 | 
			
		||||
    Enum,
 | 
			
		||||
    FlagsEnum,
 | 
			
		||||
    GreedyRange,
 | 
			
		||||
    IfThenElse,
 | 
			
		||||
    Int8ub,
 | 
			
		||||
    Optional,
 | 
			
		||||
    Struct,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
PROP_ID_RETURN = 1
 | 
			
		||||
PROP_INFO_RETURN = 2
 | 
			
		||||
PROP_SCHEDULE_SET = 0x10
 | 
			
		||||
PROP_SCHEDULE_RETURN = 0x21
 | 
			
		||||
 | 
			
		||||
NAME_TO_DAY = {"sat": 0, "sun": 1, "mon": 2, "tue": 3, "wed": 4, "thu": 5, "fri": 6}
 | 
			
		||||
NAME_TO_CMD = {"write": PROP_SCHEDULE_SET, "response": PROP_SCHEDULE_RETURN}
 | 
			
		||||
HOUR_24_PLACEHOLDER = 1234
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TimeAdapter(Adapter):
 | 
			
		||||
    """Adapter to encode and decode schedule times."""
 | 
			
		||||
 | 
			
		||||
    def _decode(self, obj, ctx, path):
 | 
			
		||||
        h, m = divmod(obj * 10, 60)
 | 
			
		||||
        if h == 24:  # HACK, can we do better?
 | 
			
		||||
            return HOUR_24_PLACEHOLDER
 | 
			
		||||
        return time(hour=h, minute=m)
 | 
			
		||||
 | 
			
		||||
    def _encode(self, obj, ctx, path):
 | 
			
		||||
        # TODO: encode h == 24 hack
 | 
			
		||||
        if obj == HOUR_24_PLACEHOLDER:
 | 
			
		||||
            return int(24 * 60 / 10)
 | 
			
		||||
        encoded = int((obj.hour * 60 + obj.minute) / 10)
 | 
			
		||||
        return encoded
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TempAdapter(Adapter):
 | 
			
		||||
    """Adapter to encode and decode temperature."""
 | 
			
		||||
 | 
			
		||||
    def _decode(self, obj, ctx, path):
 | 
			
		||||
        return float(obj / 2.0)
 | 
			
		||||
 | 
			
		||||
    def _encode(self, obj, ctx, path):
 | 
			
		||||
        return int(obj * 2.0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WindowOpenTimeAdapter(Adapter):
 | 
			
		||||
    """Adapter to encode and decode window open times (5 min increments)."""
 | 
			
		||||
 | 
			
		||||
    def _decode(self, obj, context, path):
 | 
			
		||||
        return timedelta(minutes=float(obj * 5.0))
 | 
			
		||||
 | 
			
		||||
    def _encode(self, obj, context, path):
 | 
			
		||||
        if isinstance(obj, timedelta):
 | 
			
		||||
            obj = obj.seconds
 | 
			
		||||
        if 0 <= obj <= 3600.0:
 | 
			
		||||
            return int(obj / 300.0)
 | 
			
		||||
        raise ValueError(
 | 
			
		||||
            "Window open time must be between 0 and 60 minutes "
 | 
			
		||||
            "in intervals of 5 minutes."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TempOffsetAdapter(Adapter):
 | 
			
		||||
    """Adapter to encode and decode the temperature offset."""
 | 
			
		||||
 | 
			
		||||
    def _decode(self, obj, context, path):
 | 
			
		||||
        return float((obj - 7) / 2.0)
 | 
			
		||||
 | 
			
		||||
    def _encode(self, obj, context, path):
 | 
			
		||||
        if -3.5 <= obj <= 3.5:
 | 
			
		||||
            return int(obj * 2.0) + 7
 | 
			
		||||
        raise ValueError(
 | 
			
		||||
            "Temperature offset must be between -3.5 and 3.5 (in " "intervals of 0.5)."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ModeFlags = "ModeFlags" / FlagsEnum(
 | 
			
		||||
    Int8ub,
 | 
			
		||||
    AUTO=0x00,  # always True, doesnt affect building
 | 
			
		||||
    MANUAL=0x01,
 | 
			
		||||
    AWAY=0x02,
 | 
			
		||||
    BOOST=0x04,
 | 
			
		||||
    DST=0x08,
 | 
			
		||||
    WINDOW=0x10,
 | 
			
		||||
    LOCKED=0x20,
 | 
			
		||||
    UNKNOWN=0x40,
 | 
			
		||||
    LOW_BATTERY=0x80,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AwayDataAdapter(Adapter):
 | 
			
		||||
    """Adapter to encode and decode away data."""
 | 
			
		||||
 | 
			
		||||
    def _decode(self, obj, ctx, path):
 | 
			
		||||
        (day, year, hour_min, month) = obj
 | 
			
		||||
        year += 2000
 | 
			
		||||
 | 
			
		||||
        min = 0
 | 
			
		||||
        if hour_min & 0x01:
 | 
			
		||||
            min = 30
 | 
			
		||||
        hour = int(hour_min / 2)
 | 
			
		||||
 | 
			
		||||
        return datetime(year=year, month=month, day=day, hour=hour, minute=min)
 | 
			
		||||
 | 
			
		||||
    def _encode(self, obj, ctx, path):
 | 
			
		||||
        if obj.year < 2000 or obj.year > 2099:
 | 
			
		||||
            raise Exception("Invalid year, possible [2000,2099]")
 | 
			
		||||
        year = obj.year - 2000
 | 
			
		||||
        hour = obj.hour * 2
 | 
			
		||||
        if obj.minute:  # we encode all minute values to h:30
 | 
			
		||||
            hour |= 0x01
 | 
			
		||||
        return (obj.day, year, hour, obj.month)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DeviceSerialAdapter(Adapter):
 | 
			
		||||
    """Adapter to decode the device serial number."""
 | 
			
		||||
 | 
			
		||||
    def _decode(self, obj, context, path):
 | 
			
		||||
        return bytearray(n - 0x30 for n in obj).decode()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Status = "Status" / Struct(
 | 
			
		||||
    "cmd" / Const(PROP_INFO_RETURN, Int8ub),
 | 
			
		||||
    Const(0x01, Int8ub),
 | 
			
		||||
    "mode" / ModeFlags,
 | 
			
		||||
    "valve" / Int8ub,
 | 
			
		||||
    Const(0x04, Int8ub),
 | 
			
		||||
    "target_temp" / TempAdapter(Int8ub),
 | 
			
		||||
    "away"
 | 
			
		||||
    / IfThenElse(  # noqa: W503
 | 
			
		||||
        lambda ctx: ctx.mode.AWAY, AwayDataAdapter(Bytes(4)), Optional(Bytes(4))
 | 
			
		||||
    ),
 | 
			
		||||
    "presets"
 | 
			
		||||
    / Optional(  # noqa: W503
 | 
			
		||||
        Struct(
 | 
			
		||||
            "window_open_temp" / TempAdapter(Int8ub),
 | 
			
		||||
            "window_open_time" / WindowOpenTimeAdapter(Int8ub),
 | 
			
		||||
            "comfort_temp" / TempAdapter(Int8ub),
 | 
			
		||||
            "eco_temp" / TempAdapter(Int8ub),
 | 
			
		||||
            "offset" / TempOffsetAdapter(Int8ub),
 | 
			
		||||
        )
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
Schedule = "Schedule" / Struct(
 | 
			
		||||
    "cmd" / Enum(Int8ub, **NAME_TO_CMD),
 | 
			
		||||
    "day" / Enum(Int8ub, **NAME_TO_DAY),
 | 
			
		||||
    "base_temp" / TempAdapter(Int8ub),
 | 
			
		||||
    "next_change_at" / TimeAdapter(Int8ub),
 | 
			
		||||
    "hours"
 | 
			
		||||
    / GreedyRange(  # noqa: W503
 | 
			
		||||
        Struct(
 | 
			
		||||
            "target_temp" / TempAdapter(Int8ub),
 | 
			
		||||
            "next_change_at" / TimeAdapter(Int8ub),
 | 
			
		||||
        )
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DeviceId = "DeviceId" / Struct(
 | 
			
		||||
    "cmd" / Const(PROP_ID_RETURN, Int8ub),
 | 
			
		||||
    "version" / Int8ub,
 | 
			
		||||
    Int8ub,
 | 
			
		||||
    Int8ub,
 | 
			
		||||
    "serial" / DeviceSerialAdapter(Bytes(10)),
 | 
			
		||||
    Int8ub,
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/tests/__pycache__/test_thermostat.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								deps/lib/python3.10/site-packages/eq3bt/tests/__pycache__/test_thermostat.cpython-310.pyc
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										198
									
								
								deps/lib/python3.10/site-packages/eq3bt/tests/test_thermostat.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								deps/lib/python3.10/site-packages/eq3bt/tests/test_thermostat.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,198 @@
 | 
			
		||||
import codecs
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from unittest import TestCase
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from eq3bt import TemperatureException, Thermostat
 | 
			
		||||
from eq3bt.eq3btsmart import PROP_ID_QUERY, PROP_INFO_QUERY, PROP_NTFY_HANDLE, Mode
 | 
			
		||||
 | 
			
		||||
ID_RESPONSE = b"01780000807581626163606067659e"
 | 
			
		||||
STATUS_RESPONSES = {
 | 
			
		||||
    "auto": b"020100000428",
 | 
			
		||||
    "manual": b"020101000428",
 | 
			
		||||
    "window": b"020110000428",
 | 
			
		||||
    "away": b"0201020004231d132e03",
 | 
			
		||||
    "boost": b"020104000428",
 | 
			
		||||
    "low_batt": b"020180000428",
 | 
			
		||||
    "valve_at_22": b"020100160428",
 | 
			
		||||
    "presets": b"020100000422000000001803282207",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeConnection:
 | 
			
		||||
    def __init__(self, _iface, mac):
 | 
			
		||||
        self._callbacks = {}
 | 
			
		||||
        self._res = "auto"
 | 
			
		||||
 | 
			
		||||
    def set_callback(self, handle, cb):
 | 
			
		||||
        self._callbacks[handle] = cb
 | 
			
		||||
 | 
			
		||||
    def set_status(self, key):
 | 
			
		||||
        if key in STATUS_RESPONSES:
 | 
			
		||||
            self._res = key
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError("Invalid key for status test response.")
 | 
			
		||||
 | 
			
		||||
    def make_request(self, handle, value, timeout=1, with_response=True):
 | 
			
		||||
        """Write a GATT Command without callback - not utf-8."""
 | 
			
		||||
        if with_response:
 | 
			
		||||
            cb = self._callbacks.get(PROP_NTFY_HANDLE)
 | 
			
		||||
 | 
			
		||||
            if value[0] == PROP_ID_QUERY:
 | 
			
		||||
                data = ID_RESPONSE
 | 
			
		||||
            elif value[0] == PROP_INFO_QUERY:
 | 
			
		||||
                data = STATUS_RESPONSES[self._res]
 | 
			
		||||
            else:
 | 
			
		||||
                return
 | 
			
		||||
            cb(codecs.decode(data, "hex"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestThermostat(TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.thermostat = Thermostat(
 | 
			
		||||
            _mac=None, _iface=None, connection_cls=FakeConnection
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test__verify_temperature(self):
 | 
			
		||||
        with self.assertRaises(TemperatureException):
 | 
			
		||||
            self.thermostat._verify_temperature(-1)
 | 
			
		||||
        with self.assertRaises(TemperatureException):
 | 
			
		||||
            self.thermostat._verify_temperature(35)
 | 
			
		||||
 | 
			
		||||
        self.thermostat._verify_temperature(8)
 | 
			
		||||
        self.thermostat._verify_temperature(25)
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_parse_schedule(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_handle_notification(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    def test_query_id(self):
 | 
			
		||||
        self.thermostat.query_id()
 | 
			
		||||
        self.assertEqual(self.thermostat.firmware_version, 120)
 | 
			
		||||
        self.assertEqual(self.thermostat.device_serial, "PEQ2130075")
 | 
			
		||||
 | 
			
		||||
    def test_update(self):
 | 
			
		||||
        th = self.thermostat
 | 
			
		||||
 | 
			
		||||
        th._conn.set_status("auto")
 | 
			
		||||
        th.update()
 | 
			
		||||
        self.assertEqual(th.valve_state, 0)
 | 
			
		||||
        self.assertEqual(th.mode, Mode.Auto)
 | 
			
		||||
        self.assertEqual(th.target_temperature, 20.0)
 | 
			
		||||
        self.assertFalse(th.locked)
 | 
			
		||||
        self.assertFalse(th.low_battery)
 | 
			
		||||
        self.assertFalse(th.boost)
 | 
			
		||||
        self.assertFalse(th.window_open)
 | 
			
		||||
 | 
			
		||||
        th._conn.set_status("manual")
 | 
			
		||||
        th.update()
 | 
			
		||||
        self.assertTrue(th.mode, Mode.Manual)
 | 
			
		||||
 | 
			
		||||
        th._conn.set_status("away")
 | 
			
		||||
        th.update()
 | 
			
		||||
        self.assertEqual(th.mode, Mode.Away)
 | 
			
		||||
        self.assertEqual(th.target_temperature, 17.5)
 | 
			
		||||
        self.assertEqual(th.away_end, datetime(2019, 3, 29, 23, 00))
 | 
			
		||||
 | 
			
		||||
        th._conn.set_status("boost")
 | 
			
		||||
        th.update()
 | 
			
		||||
        self.assertTrue(th.boost)
 | 
			
		||||
        self.assertEqual(th.mode, Mode.Boost)
 | 
			
		||||
 | 
			
		||||
    def test_presets(self):
 | 
			
		||||
        th = self.thermostat
 | 
			
		||||
        self.thermostat._conn.set_status("presets")
 | 
			
		||||
        self.thermostat.update()
 | 
			
		||||
        self.assertEqual(th.window_open_temperature, 12.0)
 | 
			
		||||
        self.assertEqual(th.window_open_time, timedelta(minutes=15.0))
 | 
			
		||||
        self.assertEqual(th.comfort_temperature, 20.0)
 | 
			
		||||
        self.assertEqual(th.eco_temperature, 17.0)
 | 
			
		||||
        self.assertEqual(th.temperature_offset, 0)
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_query_schedule(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_schedule(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_set_schedule(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_target_temperature(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_mode(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_mode_readable(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_boost(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    def test_valve_state(self):
 | 
			
		||||
        th = self.thermostat
 | 
			
		||||
        th._conn.set_status("valve_at_22")
 | 
			
		||||
        th.update()
 | 
			
		||||
        self.assertEqual(th.valve_state, 22)
 | 
			
		||||
 | 
			
		||||
    def test_window_open(self):
 | 
			
		||||
        th = self.thermostat
 | 
			
		||||
        th._conn.set_status("window")
 | 
			
		||||
        th.update()
 | 
			
		||||
        self.assertTrue(th.window_open)
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_window_open_config(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_locked(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_low_battery(self):
 | 
			
		||||
        th = self.thermostat
 | 
			
		||||
        th._conn.set_status("low_batt")
 | 
			
		||||
        th.update()
 | 
			
		||||
        self.assertTrue(th.low_battery)
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_temperature_offset(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_activate_comfort(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_activate_eco(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_min_temp(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_max_temp(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_away_end(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.skip()
 | 
			
		||||
    def test_decode_mode(self):
 | 
			
		||||
        self.fail()
 | 
			
		||||
							
								
								
									
										1
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/INSTALLER
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/INSTALLER
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
pip
 | 
			
		||||
							
								
								
									
										21
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
The MIT License (MIT)
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2016 Markus Peter
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
							
								
								
									
										230
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/METADATA
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/METADATA
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,230 @@
 | 
			
		||||
Metadata-Version: 2.1
 | 
			
		||||
Name: python-eq3bt
 | 
			
		||||
Version: 0.2
 | 
			
		||||
Summary: EQ3 bluetooth thermostat support library
 | 
			
		||||
Home-page: https://github.com/rytilahti/python-eq3bt
 | 
			
		||||
License: MIT
 | 
			
		||||
Author: Teemu R.
 | 
			
		||||
Author-email: tpr@iki.fi
 | 
			
		||||
Requires-Python: >=3.7,<4.0
 | 
			
		||||
Classifier: License :: OSI Approved :: MIT License
 | 
			
		||||
Classifier: Programming Language :: Python :: 3
 | 
			
		||||
Classifier: Programming Language :: Python :: 3.10
 | 
			
		||||
Classifier: Programming Language :: Python :: 3.7
 | 
			
		||||
Classifier: Programming Language :: Python :: 3.8
 | 
			
		||||
Classifier: Programming Language :: Python :: 3.9
 | 
			
		||||
Provides-Extra: bluepy
 | 
			
		||||
Provides-Extra: gattlib
 | 
			
		||||
Requires-Dist: bleak
 | 
			
		||||
Requires-Dist: bluepy (>=1.0.5); extra == "bluepy"
 | 
			
		||||
Requires-Dist: click
 | 
			
		||||
Requires-Dist: construct
 | 
			
		||||
Requires-Dist: gattlib; extra == "gattlib"
 | 
			
		||||
Project-URL: Repository, https://github.com/rytilahti/python-eq3bt
 | 
			
		||||
Description-Content-Type: text/markdown
 | 
			
		||||
 | 
			
		||||
# python-eq3bt
 | 
			
		||||
 | 
			
		||||
Python library and a command line tool for EQ3 Bluetooth smart thermostats, uses bleak (default), bluepy or gattlib for BTLE communication.
 | 
			
		||||
 | 
			
		||||
# Features
 | 
			
		||||
 | 
			
		||||
* Reading device status: locked, low battery, valve state, window open, target temperature, active mode
 | 
			
		||||
* Writing settings: target temperature, auto mode presets, temperature offset
 | 
			
		||||
* Setting the active mode: auto, manual, boost, away
 | 
			
		||||
* Reading the device serial number and firmware version
 | 
			
		||||
* Reading presets and temperature offset in more recent firmware versions.
 | 
			
		||||
 | 
			
		||||
## Not (yet) supported
 | 
			
		||||
 | 
			
		||||
* No easy-to-use interface for setting schedules.
 | 
			
		||||
 | 
			
		||||
# Installation
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
pip install python-eq3bt
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# Command-line Usage
 | 
			
		||||
 | 
			
		||||
To test all available functionality a cli tool inside utils can be used:
 | 
			
		||||
```
 | 
			
		||||
$ eq3cli  --help
 | 
			
		||||
Usage: eq3cli [OPTIONS] COMMAND [ARGS]...
 | 
			
		||||
 | 
			
		||||
  Tool to query and modify the state of EQ3 BT smart thermostat.
 | 
			
		||||
 | 
			
		||||
Options:
 | 
			
		||||
  --mac TEXT                  [required]
 | 
			
		||||
  --interface TEXT
 | 
			
		||||
  --debug / --normal
 | 
			
		||||
  --backend [bleak|bluepy|gattlib]
 | 
			
		||||
  --help                      Show this message and exit.
 | 
			
		||||
 | 
			
		||||
Commands:
 | 
			
		||||
  away         Enables or disables the away mode.
 | 
			
		||||
  boost        Gets or sets the boost mode.
 | 
			
		||||
  device       Displays basic device information.
 | 
			
		||||
  locked       Gets or sets the lock.
 | 
			
		||||
  low-battery  Gets the low battery status.
 | 
			
		||||
  mode         Gets or sets the active mode.
 | 
			
		||||
  offset       Sets the temperature offset [-3,5 3,5]
 | 
			
		||||
  presets      Sets the preset temperatures for auto mode.
 | 
			
		||||
  schedule     Gets the schedule from the thermostat.
 | 
			
		||||
  state        Prints out all available information.
 | 
			
		||||
  temp         Gets or sets the target temperature.
 | 
			
		||||
  valve-state  Gets the state of the valve.
 | 
			
		||||
  window-open  Gets and sets the window open settings.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
EQ3_MAC environment variable can be used to define mac to avoid typing it:
 | 
			
		||||
```bash
 | 
			
		||||
export EQ3_MAC=XX:XX
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Without parameters current state of the device is printed out.
 | 
			
		||||
```bash
 | 
			
		||||
$ eq3cli
 | 
			
		||||
 | 
			
		||||
[00:1A:22:XX:XX:XX] Target 17.0 (mode: auto dst, away: no)
 | 
			
		||||
Locked: False
 | 
			
		||||
Batter low: False
 | 
			
		||||
Window open: False
 | 
			
		||||
Window open temp: 12.0
 | 
			
		||||
Window open time: 0:15:00
 | 
			
		||||
Boost: False
 | 
			
		||||
Current target temp: 17.0
 | 
			
		||||
Current comfort temp: 20.0
 | 
			
		||||
Current eco temp: 17.0
 | 
			
		||||
Current mode: auto dst locked
 | 
			
		||||
Valve: 0
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Getting & setting values.
 | 
			
		||||
```bash
 | 
			
		||||
$ eq3cli temp
 | 
			
		||||
 | 
			
		||||
Current target temp: 17.0
 | 
			
		||||
 | 
			
		||||
eq3cli temp --target 20
 | 
			
		||||
 | 
			
		||||
Current target temp: 17.0
 | 
			
		||||
Setting target temp: 20.0
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# Pairing
 | 
			
		||||
 | 
			
		||||
If you have thermostat with firmware version 1.20+ pairing may be needed. Below simple procedure to do that.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Press and hold wheel on thermostat until Pair will be displayed. Remember or write it.
 | 
			
		||||
 | 
			
		||||
$ sudo bluetoothctl
 | 
			
		||||
[bluetooth]# power on
 | 
			
		||||
[bluetooth]# agent on
 | 
			
		||||
[bluetooth]# default-agent
 | 
			
		||||
[bluetooth]# scan on
 | 
			
		||||
[bluetooth]# scan off
 | 
			
		||||
[bluetooth]# pair 00:1A:22:06:A7:83
 | 
			
		||||
[agent] Enter passkey (number in 0-999999): <enter pin>
 | 
			
		||||
[bluetooth]# trust 00:1A:22:06:A7:83
 | 
			
		||||
[bluetooth]# disconnect 00:1A:22:06:A7:83
 | 
			
		||||
[bluetooth]# exit
 | 
			
		||||
 | 
			
		||||
Optional steps:
 | 
			
		||||
[bluetooth]# devices - to list all bluetooth devices
 | 
			
		||||
[bluetooth]# info 00:1A:22:06:A7:83
 | 
			
		||||
Device 00:1A:22:06:A7:83 (public)
 | 
			
		||||
	Name: CC-RT-BLE
 | 
			
		||||
	Alias: CC-RT-BLE
 | 
			
		||||
	Paired: yes
 | 
			
		||||
	Trusted: yes
 | 
			
		||||
	Blocked: no
 | 
			
		||||
	Connected: no
 | 
			
		||||
	LegacyPairing: no
 | 
			
		||||
	UUID: Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)
 | 
			
		||||
	UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
 | 
			
		||||
	UUID: Device Information        (0000180a-0000-1000-8000-00805f9b34fb)
 | 
			
		||||
	UUID: Vendor specific           (3e135142-654f-9090-134a-a6ff5bb77046)
 | 
			
		||||
	UUID: Vendor specific           (9e5d1e47-5c13-43a0-8635-82ad38a1386f)
 | 
			
		||||
	ManufacturerData Key: 0x0000
 | 
			
		||||
	ManufacturerData Value:
 | 
			
		||||
  00 00 00 00 00 00 00 00 00                       .........
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Be aware that sometimes if you pair your device then mobile application (calor BT) can't connect with thermostat and vice versa.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Library Usage
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
from eq3bt import Thermostat
 | 
			
		||||
 | 
			
		||||
thermostat = Thermostat('AB:CD:EF:12:23:45')
 | 
			
		||||
thermostat.update()  # fetches data from the thermostat
 | 
			
		||||
 | 
			
		||||
print(thermostat)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<aside class="notice">
 | 
			
		||||
Notice: The device in question has to be disconnected from bluetoothd, since BTLE devices can only hold one connection at a time.
 | 
			
		||||
 | 
			
		||||
The library will try to connect to the device second time in case it wasn't successful in the first time,
 | 
			
		||||
which can happen if you are running other applications connecting to the same thermostat.
 | 
			
		||||
</aside>
 | 
			
		||||
 | 
			
		||||
## Fetching schedule
 | 
			
		||||
 | 
			
		||||
The schedule is queried per day basis and the cached information can be
 | 
			
		||||
accessed through `schedule` property..
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
from eq3bt import Thermostat
 | 
			
		||||
 | 
			
		||||
thermostat = Thermostat('AB:CD:EF:12:34:45')
 | 
			
		||||
thermostat.query_schedule(0)
 | 
			
		||||
print(thermostat.schedule)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Setting schedule
 | 
			
		||||
 | 
			
		||||
The 'base_temp' and 'next_change_at' paramater define the first period for that 'day' (the period from midnight up till next_change_at).
 | 
			
		||||
 | 
			
		||||
The schedule can be set on a per day basis like follows:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
from datetime import time
 | 
			
		||||
from eq3bt import Thermostat
 | 
			
		||||
from eq3bt import HOUR_24_PLACEHOLDER as END_OF_DAY
 | 
			
		||||
 | 
			
		||||
thermostat = Thermostat('12:34:56:78:9A:BC')
 | 
			
		||||
thermostat.set_schedule(
 | 
			
		||||
    dict(
 | 
			
		||||
        cmd="write",
 | 
			
		||||
        day="sun",
 | 
			
		||||
        base_temp=18,
 | 
			
		||||
        next_change_at=time(8, 0),
 | 
			
		||||
        hours=[
 | 
			
		||||
            dict(target_temp=23, next_change_at=time(20, 0)),
 | 
			
		||||
            dict(target_temp=18, next_change_at=END_OF_DAY),
 | 
			
		||||
            dict(target_temp=23, next_change_at=END_OF_DAY),
 | 
			
		||||
            dict(target_temp=23, next_change_at=END_OF_DAY),
 | 
			
		||||
            dict(target_temp=23, next_change_at=END_OF_DAY),
 | 
			
		||||
            dict(target_temp=23, next_change_at=END_OF_DAY)
 | 
			
		||||
        ]
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# Contributing
 | 
			
		||||
 | 
			
		||||
Feel free to open pull requests to improve the library!
 | 
			
		||||
 | 
			
		||||
This project uses github actions to enforce code formatting using tools like black, isort, flake8, and mypy.
 | 
			
		||||
You can run these checks locally either by executing `pre-commit run -a` or using `tox` which also runs the test suite.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# History
 | 
			
		||||
 | 
			
		||||
This library is a simplified version of bluepy_devices from Markus Peter (https://github.com/bimbar/bluepy_devices.git) with support for more features and robuster device handling.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										25
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/RECORD
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/RECORD
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
../../../bin/eq3cli,sha256=TFR4GGzCskn7h5eKh4qUtn2HO-VX7rl4O4VKAFMH92Y,215
 | 
			
		||||
CHANGELOG,sha256=FVxHhLou6OnfINd-OStO2tzYAjsalbowKDL61Fr9Z-Y,6002
 | 
			
		||||
eq3bt/__init__.py,sha256=fUKZ9G7dFcM5wEOIJdOUAUcyJBeDODsdeVCk2J-w3q0,189
 | 
			
		||||
eq3bt/__pycache__/__init__.cpython-310.pyc,,
 | 
			
		||||
eq3bt/__pycache__/bleakconnection.cpython-310.pyc,,
 | 
			
		||||
eq3bt/__pycache__/connection.cpython-310.pyc,,
 | 
			
		||||
eq3bt/__pycache__/eq3btsmart.cpython-310.pyc,,
 | 
			
		||||
eq3bt/__pycache__/eq3cli.cpython-310.pyc,,
 | 
			
		||||
eq3bt/__pycache__/gattlibconnection.cpython-310.pyc,,
 | 
			
		||||
eq3bt/__pycache__/structures.cpython-310.pyc,,
 | 
			
		||||
eq3bt/bleakconnection.py,sha256=E5QWp0fYUDR150zBB8xtY68cRThADXpEUei5F6hTCQo,4269
 | 
			
		||||
eq3bt/connection.py,sha256=j2E2Nugo3BeteHFY3uozfb_8YZxWB5Y1itNa_SGQtlk,3217
 | 
			
		||||
eq3bt/eq3btsmart.py,sha256=st-NMiIeuXOR6EYBESkKWyRnMfrFpzZq6b4P-sOy7Sk,15716
 | 
			
		||||
eq3bt/eq3cli.py,sha256=_YFvGT0XsHE3WdGW0jp1NaH_yrRnYDpvmwBJHPOm7io,6219
 | 
			
		||||
eq3bt/gattlibconnection.py,sha256=25s9pY0azzwEUfJsTRXcFs0SPeoOKwb2Tx4QUsL5w_g,3285
 | 
			
		||||
eq3bt/structures.py,sha256=rSFQ6eTYR-18A9WEcNPcBb2P8S-G-u9gx5jVhDIjgl0,4635
 | 
			
		||||
eq3bt/tests/__pycache__/test_thermostat.cpython-310.pyc,,
 | 
			
		||||
eq3bt/tests/test_thermostat.py,sha256=lDLCNvCNgVOJoa6JUEXvAtvfhkLZR2UJZh7_XvXGQbI,5381
 | 
			
		||||
python_eq3bt-0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
 | 
			
		||||
python_eq3bt-0.2.dist-info/LICENSE,sha256=og5_OL0-NkYbkZwhvFpb4hR3EfM-0IXaore85BAkhLw,1079
 | 
			
		||||
python_eq3bt-0.2.dist-info/METADATA,sha256=gjGWD8CeJ_uZqr_JTfQd4wCfRErXhUxoLuJs5l8F3KA,6842
 | 
			
		||||
python_eq3bt-0.2.dist-info/RECORD,,
 | 
			
		||||
python_eq3bt-0.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
 | 
			
		||||
python_eq3bt-0.2.dist-info/WHEEL,sha256=DA86_h4QwwzGeRoz62o1svYt5kGEXpoUTuTtwzoTb30,83
 | 
			
		||||
python_eq3bt-0.2.dist-info/entry_points.txt,sha256=XClUgvSU6CSK_T1HCDmeMObxQ6wKPpxMjWlZe5TiELk,43
 | 
			
		||||
							
								
								
									
										0
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/REQUESTED
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/REQUESTED
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
								
								
									
										4
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/WHEEL
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/WHEEL
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
Wheel-Version: 1.0
 | 
			
		||||
Generator: poetry 1.0.8
 | 
			
		||||
Root-Is-Purelib: true
 | 
			
		||||
Tag: py3-none-any
 | 
			
		||||
							
								
								
									
										3
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/entry_points.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								deps/lib/python3.10/site-packages/python_eq3bt-0.2.dist-info/entry_points.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
[console_scripts]
 | 
			
		||||
eq3cli=eq3bt.eq3cli:cli
 | 
			
		||||
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
notify:
 | 
			
		||||
  - platform: html5
 | 
			
		||||
    name: browser
 | 
			
		||||
    vapid_pub_key: !secret  google_api_pub
 | 
			
		||||
    vapid_prv_key: !secret  google_api_priv
 | 
			
		||||
    vapid_email: !secret  google_api_email
 | 
			
		||||
#notify:
 | 
			
		||||
#  - platform: html5
 | 
			
		||||
#    name: browser
 | 
			
		||||
#    vapid_pub_key: !secret  google_api_pub
 | 
			
		||||
#    vapid_prv_key: !secret  google_api_priv
 | 
			
		||||
#    vapid_email: !secret  google_api_email
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								themes/minimalist-desktop/minimalist-desktop.yaml
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										4
									
								
								themes/minimalist-desktop/minimalist-desktop.yaml
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -10,6 +10,10 @@ minimalist-desktop:
 | 
			
		||||
  info-color: "var(--google-blue)"
 | 
			
		||||
  divider-color: "rgba(var(--color-theme),.12)"
 | 
			
		||||
  accent-color: "var(--google-yellow)"
 | 
			
		||||
  ha-dialog-border-radius: "10px"
 | 
			
		||||
  # fix added border-lines in 2022.11
 | 
			
		||||
  ha-card-border-width: "0px"
 | 
			
		||||
 | 
			
		||||
  card-mod-theme: "minimalist-desktop"
 | 
			
		||||
  card-mod-view-yaml: |
 | 
			
		||||
    "*:first-child$": |
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								themes/minimalist-ios-tapbar/minimalist-ios-tapbar.yaml
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										4
									
								
								themes/minimalist-ios-tapbar/minimalist-ios-tapbar.yaml
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -12,6 +12,10 @@ minimalist-ios-tapbar:
 | 
			
		||||
  info-color: "var(--google-blue)"
 | 
			
		||||
  divider-color: "rgba(var(--color-theme),.12)"
 | 
			
		||||
  accent-color: "var(--google-yellow)"
 | 
			
		||||
  ha-dialog-border-radius: "10px"
 | 
			
		||||
  # fix added border-lines in 2022.11
 | 
			
		||||
  ha-card-border-width: "0px"
 | 
			
		||||
 | 
			
		||||
  card-mod-theme: "minimalist-ios-tapbar"
 | 
			
		||||
  card-mod-root: |
 | 
			
		||||
    app-toolbar {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								themes/minimalist-mobile-tapbar/minimalist-mobile-tapbar.yaml
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										3
									
								
								themes/minimalist-mobile-tapbar/minimalist-mobile-tapbar.yaml
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -14,6 +14,9 @@ minimalist-mobile-tapbar:
 | 
			
		||||
  header-height: "calc(var(--header-base-height) + env(safe-area-inset-bottom))"
 | 
			
		||||
  header-base-height: "70px"
 | 
			
		||||
  app-header-selection-bar-color: "transparent"
 | 
			
		||||
  ha-dialog-border-radius: "10px"
 | 
			
		||||
  # fix added border-lines in 2022.11
 | 
			
		||||
  ha-card-border-width: "0px"
 | 
			
		||||
 | 
			
		||||
  card-mod-view-yaml: |
 | 
			
		||||
    "*:first-child$": |
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								themes/minimalist-mobile/minimalist-mobile.yaml
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										4
									
								
								themes/minimalist-mobile/minimalist-mobile.yaml
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -10,6 +10,10 @@ minimalist-mobile:
 | 
			
		||||
  info-color: "var(--google-blue)"
 | 
			
		||||
  divider-color: "rgba(var(--color-theme),.12)"
 | 
			
		||||
  accent-color: "var(--google-yellow)"
 | 
			
		||||
  ha-dialog-border-radius: "10px"
 | 
			
		||||
  # fix added border-lines in 2022.11
 | 
			
		||||
  ha-card-border-width: "0px"
 | 
			
		||||
 | 
			
		||||
  card-mod-theme: "minimalist-mobile"
 | 
			
		||||
  card-mod-root: |
 | 
			
		||||
    app-toolbar {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user