Compare commits

..

326 Commits

Author SHA1 Message Date
Paulus Schoutsen e9fa1f1f83 Merge pull request #1182 from balloob/dev
0.13
2016-02-12 21:55:24 -08:00
Paulus Schoutsen 724d5bfe9d Version bump to 0.13 2016-02-12 21:51:34 -08:00
Paulus Schoutsen 938c9888a6 Merge pull request #918 from happyleavesaoc/snapcast
snapcast media player
2016-02-12 19:31:07 -08:00
Paulus Schoutsen b91e4cfb4a Merge pull request #1195 from persandstrom/kodi_thumbnails
kodi thumbnails
2016-02-12 19:03:22 -08:00
Paulus Schoutsen 7dd51034cd Merge pull request #1213 from MartinHjelmare/mysensors-light
Add mysensors light platform
2016-02-12 19:01:41 -08:00
Paulus Schoutsen 2f60ff224f Merge pull request #1223 from MartinHjelmare/update-mysensors-version
Update pymysensors version to 0.5
2016-02-12 18:55:06 -08:00
Paulus Schoutsen fc3a37cba2 Merge pull request #1224 from MartinHjelmare/fix-mysensors-version-error
Fix error in update method for V_STATUS
2016-02-12 18:54:12 -08:00
Paulus Schoutsen 223b7f85ee Merge pull request #1225 from nkgilley/ecobee-fix
fix ecobee sensor unique id.
2016-02-12 18:53:58 -08:00
happyleavesaoc 13b0beee31 snapcast 2016-02-12 18:35:32 -05:00
nkgilley@gmail.com ba530e5a16 fix ecobee sensor unique id. 2016-02-12 17:24:00 -05:00
Per SandstrÃom aafd36d2ce change url to host 2016-02-12 21:19:27 +00:00
MartinHjelmare 0492f0abd0 Fix error in update method for V_STATUS
* Remove check against V_STATUS to avoid error when using version 1.4
    of mysensors. V_LIGHT has the same integer value so V_STATUS is not
    needed.
2016-02-12 22:05:31 +01:00
MartinHjelmare 940799d0da Update pymysensors version to 0.5 2016-02-12 21:59:24 +01:00
Paulus Schoutsen 1ba60dc898 Update .coveragerc 2016-02-12 09:01:48 -08:00
Paulus Schoutsen 75d3d25969 Merge pull request #1129 from nkgilley/speedtest
Speedtest.net component
2016-02-12 08:59:19 -08:00
Paulus Schoutsen 06bd812b7b Make state unknown if None returned 2016-02-12 08:58:07 -08:00
nkgilley@gmail.com 7cb57583e2 fix line too long 2016-02-12 11:44:24 -05:00
nkgilley@gmail.com 3e3f5db2a5 use a windows & linux compatible regex 2016-02-12 11:30:55 -05:00
nkgilley@gmail.com 88fe28ea1b add update service 2016-02-12 09:55:28 -05:00
Paulus Schoutsen 8857c48c17 Merge pull request #1206 from turbokongen/rfxtrx-newprotocols
Supporting electricity sensors with ELEC2/3 protocol from latest pyRFXtrx (0.4)
2016-02-11 23:30:40 -08:00
Paulus Schoutsen 3c582d1e3c Merge pull request #1143 from balloob/rfxtrx
Rfxtrx
2016-02-11 23:30:34 -08:00
Fabian Affolter 19d12716ef Update docstrings 2016-02-12 08:21:39 +01:00
Fabian Affolter 57446cfb08 Add link to docs 2016-02-12 07:55:20 +01:00
Paulus Schoutsen ff6cb2b452 Update frontend icons + proximity icon 2016-02-11 22:21:06 -08:00
Paulus Schoutsen a3ec7998b1 Update frontend with garage door support 2016-02-11 21:55:50 -08:00
Paulus Schoutsen d892716bfa Merge pull request #1218 from kk7ds/influx-tests
Add tests for influxdb and fix a bug
2016-02-11 21:45:37 -08:00
Dan Smith 61e2da8827 Add tests for influxdb and fix a bug
This adds tests for the influxdb component. It also fixes a bug,
where username and password are required, but not gracefully
handled if they're missing from config.
2016-02-12 05:34:13 +00:00
Paulus Schoutsen abc039a3d0 Check coverage for statsd and splunk 2016-02-11 20:22:01 -08:00
Paulus Schoutsen 7ba7747ce8 Merge pull request #1217 from jaharkes/nest_heating
Nest binary sensor fixes
2016-02-11 20:15:02 -08:00
Jan Harkes b04ff7207c Make sure Nest is setup before the binary sensors. 2016-02-11 21:28:08 -05:00
Dan Smith 72235c48fb Merge pull request #1216 from kk7ds/fix-dan-broke-splunk
Re-allow splunk to report string states
2016-02-11 18:26:24 -08:00
Jan Harkes 3f7ff2b1d4 The Nest binary sensor can also track when heating system is running. 2016-02-11 21:24:51 -05:00
Dan Smith 484b7b64d7 Re-allow splunk to report string states
Splunk *can* take string states, so un-fix that wrong fix.
2016-02-12 02:20:15 +00:00
Paulus Schoutsen 7241762bcc Merge pull request #1209 from kk7ds/abstract-numeric-state
Abstract numeric state
2016-02-11 18:16:57 -08:00
Dan Smith 0a904acd4d Add some tests for splunk
This also fixes issue #1214, and I think another bug. The splunk
code will just take the value of state.state and try to serialize
it to json if it can't make it into a number. It did this before
I generalized that code. Since json.dumps() will fail on most anything
complicated, I think the right thing to do is *not* try to do that.
2016-02-12 01:45:30 +00:00
Dan Smith 76df759f4c Add simple statsd tests
These are not very amazing, but at least exercise the code a little
to make sure I didn't break anything. Hopefully they're useful in the
future too.
2016-02-12 01:45:30 +00:00
Dan Smith 4a2b956493 Convert statsd, influx, splunk, and graphite to use state_as_number()
Fixes #1205
2016-02-12 01:45:25 +00:00
Dan Smith 3aa34deaa2 Add state_as_number() helper
This adds state_as_number(), a helper method that tries to interpret
state as a number, for cases we can predict. It's a generalization of
what is copy-and-paste-ed into multiple other places.
2016-02-12 00:41:32 +00:00
nkgilley@gmail.com b00cad7095 fix travisci errors. 2016-02-11 19:27:05 -05:00
MartinHjelmare 2cf061c768 Add mysensors light platform
* Make light controllable independently with types V_LIGHT, V_DIMMER,
    V_RGB and V_RGBW. V_RGBW is not implemented in the frontend yet.
* Add discovery for light platform.
* Add optimistic mode config setting for switch and light
    to allow feedback state from actuator.
* Move S_LIGHT, V_LIGHT, V_STATUS types from switch to light platform.
* Change node update logging to debug from info level.
* Fix some inaccurate comments.
2016-02-12 01:22:35 +01:00
nkgilley@gmail.com e837e97c9d use track_time_change 2016-02-11 19:09:51 -05:00
Paulus Schoutsen df8afe51f4 Merge pull request #1212 from MartinHjelmare/fix-validate-config
Fix validate config in mysensors
2016-02-11 12:39:16 -08:00
Greg Dowling c8e6f89302 Merge pull request #1210 from balloob/fix_owntracks_passive_zone
Handle passive zones correctly.
2016-02-11 20:35:21 +00:00
pavoni 2e75a58372 Fix outtracks bug with passive zones. 2016-02-11 20:28:02 +00:00
MartinHjelmare ae2fd149a5 Fix validate config in mysensors
* Add check of port in config.
2016-02-11 21:03:13 +01:00
Greg Dowling ee62c2cc2b Merge pull request #1208 from kk7ds/static-wemos
Support manually-defined WeMo devices
2016-02-11 18:46:58 +00:00
Dan Smith 4cfa14c29d Support manually-defined WeMo devices
This is extremely useful if you want to support wemos that are on
another subnet or across a VPN. It also lets you sidestep the discovery
process, which is problematic for a lot of people and situations.

In order for this to work, we need to bump the pywemo requirement to
0.3.10, which includes my changes to make this possible.

WeMo devices can be manually configured by adding a static section to
the config, like this:

  switch:
    platform: wemo
    static:
       - 192.168.100.5
       - 192.168.100.6
2016-02-11 17:23:20 +00:00
Paulus Schoutsen 4ce1a67c13 Merge pull request #1077 from xrolfex/wink_garage_door_support
Wink Garage Door Support
2016-02-11 07:59:57 -08:00
Dan Smith 962463c1ab Merge pull request #1196 from kk7ds/honeywell-enumerate
Make Honeywell module enumerate all available thermostats
2016-02-11 07:53:05 -08:00
Paulus Schoutsen 74f06b6862 Merge pull request #1190 from flyte/apcupsd-component
Add APCUPSd component
2016-02-11 07:35:43 -08:00
Dan Smith d3d7d458e1 Make Honeywell module enumerate all available thermostats
This extends the HoneywellUSThermostat functionality to find and add
all thermostats in your account. So, we add a new config element called
'region' that is the primary trigger for this, and remove the 'id'
trigger since it was never in a released version.

This does a few extra things:
 - It names the thermostat what you have it named in your account,
   which is not something we get to know *unless* we emumerate.
 - It makes all thermostats on a given account use the same session,
   and thus we have to avoid doing an explicit login every time we
   refresh our data. That was causing some rate-limiting on their
   side when I was debugging, so this is probably good. Now, we use
   their existing keepalive pinger to determine if we're still logged
   in and only re-login if we need to.
2016-02-11 15:26:41 +00:00
Eric Rolf cca6b0c287 Test Fix. 2016-02-11 10:16:57 -05:00
Eric Rolf 23e3b8d2f2 Fixed Style Issue 2016-02-11 10:00:12 -05:00
Eric Rolf be9a2a043e Refactored Method Names. 2016-02-11 09:57:56 -05:00
Dan Smith cc4fa6cd38 Merge pull request #1194 from kk7ds/add-graphite-feeder
Add graphite feeder component
2016-02-11 06:36:19 -08:00
John Arild Berentsen 4d15367956 Supporting electricity sensors with ELEC2/3 protocol from latest pyRFXtrx (0.4) 2016-02-11 15:35:05 +01:00
Eric Rolf 175b49236c Fixed style attribute with redefined built in method names. 2016-02-11 09:20:47 -05:00
Eric Rolf fd0afaa204 Fixed Test Case Logic 2016-02-11 09:12:28 -05:00
Eric Rolf 034cec7152 Fixed Demo Test Cases 2016-02-11 09:06:35 -05:00
Eric Rolf f464d591c9 Update python wink requirement 2016-02-11 08:49:07 -05:00
Eric Rolf 06cb97adee Merge branch 'wink_garage_door_support' of https://github.com/xrolfex/home-assistant into wink_garage_door_support 2016-02-11 08:39:20 -05:00
Eric Rolf cab46b91e3 Updated Requirements All. 2016-02-11 08:38:05 -05:00
Eric Rolf 0da09b85de refactored test case 2016-02-11 08:37:17 -05:00
Eric Rolf 95d9bc48ea Updated Demo 2016-02-11 08:37:17 -05:00
Eric Rolf 6b962a2207 Updated coveragec, cleaned up constants, added test for demo. 2016-02-11 08:37:16 -05:00
Eric Rolf 18b3d3df57 Forgot to refactor demo. 2016-02-11 08:37:16 -05:00
Eric Rolf 5f6977acda Refactor Method Name For Open and Close. 2016-02-11 08:37:16 -05:00
Eric Rolf 89f6ef9f6c Updated requirements_all.txt 2016-02-11 08:37:16 -05:00
Eric Rolf d2ad0620ee Wink Garage Door Support 2016-02-11 08:37:16 -05:00
Eric Rolf aa13392983 refactored test case 2016-02-11 08:30:33 -05:00
Eric Rolf 6cbf19934f Updated Demo 2016-02-11 08:23:04 -05:00
Daniel f938134069 updated rfxtrx lib 2016-02-11 14:15:51 +01:00
Eric Rolf 7cdcb800a9 Updated coveragec, cleaned up constants, added test for demo. 2016-02-11 07:41:42 -05:00
Flyte 91fb2764cc Use a cache object to reduce the frequency of calls to APCUPSd 2016-02-11 07:33:53 +00:00
Paulus Schoutsen 82c5e2cf3c Merge pull request #1177 from Theb-1/dev-notify-rest
REST notify component
2016-02-10 23:05:34 -08:00
Flyte bb8981b611 Add apcupsd component. 2016-02-11 06:33:23 +00:00
Paulus Schoutsen b350f22a77 Update frontend to fix color picker 2016-02-10 22:18:08 -08:00
Paulus Schoutsen 40da28a0c7 Merge pull request #1192 from i-c/add-delay-to-launchtl-load
Add delay to launchtl load
2016-02-10 21:39:36 -08:00
Paulus Schoutsen 3bdb50510a Merge pull request #1202 from huanga/change_nest_battery-level_unit
Changing battery level unit to "V" instead of "%"
2016-02-10 21:33:10 -08:00
Dan Smith 7478c36b27 Add graphite feeder component
Like recorder, this component listens to all events and reports any
that it can to a graphite installation. This makes it easy to use
graphite for all your data collection and analysis. If you run
carbon-cache (the backend for graphite) on the local machine, no
configuration is required other than enabling the component.

For more info on graphite: http://graphite.wikidot.com/
2016-02-11 05:28:40 +00:00
Paulus Schoutsen b1f2c90bd0 Add MQTT service description 2016-02-10 21:23:27 -08:00
Ian Copp e53785f30c Fix __main__.py permissions change 2016-02-10 21:20:56 -08:00
Paulus Schoutsen 1a38354ed5 Merge pull request #1138 from flyte/mqtt-publish-template
Add template support to mqtt.publish service payload.
2016-02-10 21:13:02 -08:00
Andy Huang 02609d0ab5 Changing battery level unit to "V" instead of "%" as the API reports output voltage, not percentage.
This value matches with Settings > Technical Information's Battery information.
2016-02-10 21:12:43 -08:00
Paulus Schoutsen ce4f5ff29c Merge pull request #1197 from maxdrift/expose-baud-rate-mysensors-component
Expose baud rate config for MySensors component
2016-02-10 20:51:18 -08:00
Paulus Schoutsen 5190cc74c5 Merge pull request #1200 from MartinHjelmare/mysensors-unavailable
Use entity property available in mysensors
2016-02-10 20:45:48 -08:00
Paulus Schoutsen e83f8da342 Merge pull request #1185 from balloob/perf-states
Make State class more immutable
2016-02-10 18:53:02 -08:00
Paulus Schoutsen ddaeeba68b Merge pull request #1186 from balloob/remove-deprecated-methods
Remove deprecated methods from core
2016-02-10 18:52:57 -08:00
MartinHjelmare 75775a561b Use entity property available 2016-02-11 03:27:02 +01:00
Fabian Affolter 058315720f Fix typo 2016-02-10 23:59:34 +01:00
Flyte 4e0c7f8a3d Create additional mqtt helper function for using template payload. 2016-02-10 22:38:33 +00:00
Riccardo Massari c705ca4288 Expose baud rate config for mysensors component 2016-02-10 23:16:41 +01:00
Per SandstrÃom 15ad48a7a0 kodi thumbnails 2016-02-10 19:48:41 +00:00
Ian Copp c9c15c4cf7 Fix comment phrasing
Somehow I left out the "some". Whoops.
2016-02-10 11:32:56 -08:00
Ian Copp 3046bfce7b Add small time delay to restart-osx command 2016-02-10 11:29:25 -08:00
Flyte d52e2019c0 Update mqtt.publish() function to use template_payload. Reorganise publish service. Use mqtt.publish() in tests. 2016-02-10 11:11:02 +00:00
Paulus Schoutsen af8f6bcaba Remove deprecated methods from core 2016-02-09 23:59:31 -08:00
Daniel cdf0e80773 Improve the robustness of the rfxtrx module, and solve issue #1116 2016-02-10 08:44:34 +01:00
Paulus Schoutsen b0948bef5f Make State class immutable 2016-02-09 23:27:01 -08:00
Paulus Schoutsen 70a528c04b Merge pull request #1184 from balloob/group-of-groups
Support expanding nested groups
2016-02-09 22:56:30 -08:00
Theb-1 d796625098 flake8 fix 2016-02-09 22:51:44 -08:00
Paulus Schoutsen dc44ef7356 Support expanding nested groups 2016-02-09 22:43:07 -08:00
Theb-1 c5c4085ad4 upper/arg list/response improvements 2016-02-09 22:27:54 -08:00
Paulus Schoutsen 09b3aba51b Merge pull request #1136 from turbokongen/verisure-locks
Added support for lock connected to Verisure system.
2016-02-09 21:20:52 -08:00
Paulus Schoutsen 6f3aefde64 Merge pull request #1179 from roqeer/patch-1
Add name property to DHT sensor
2016-02-09 21:14:23 -08:00
Theb-1 b3a1491482 pylint: ignore 'too-many-arguments' 2016-02-09 21:12:33 -08:00
Theb-1 b7ff79da24 Fixes and updates
Remove setting defaults twice
Add timeout
Add optional title and target
2016-02-09 20:48:17 -08:00
roqeer 4bf4d94344 Changed to dict lookup with default value
Corrected on request
2016-02-09 22:09:44 +01:00
roqeer 3e26af5ff1 Correct bad-indentation 2016-02-09 19:09:48 +01:00
roqeer c1270cf0bb Add name property to DHT sensor
Add name to distinguish between multiple connected DHT sensors
2016-02-09 18:58:04 +01:00
Flyte 26fc637ab5 Add payload_template to mqtt 'publish' service call. 2016-02-09 15:41:31 +00:00
Fabian Affolter 66c5d96b43 Upgrade influxdb to 2.12.0 2016-02-09 16:32:05 +01:00
turbokongen@hotmail.com 41f908ed39 Added support for lock connected to Verisure system. 2016-02-09 13:17:05 +01:00
Theb-1 0f5487b95a Add REST notify component 2016-02-08 22:24:11 -08:00
Paulus Schoutsen 23c5159f6c Update frontend to fix map CSS 2016-02-08 21:58:41 -08:00
Paulus Schoutsen 4840dd297a Add unique ID to ecobee sensor 2016-02-08 20:39:09 -08:00
Dan Smith 4605742bb7 Merge pull request #1176 from kk7ds/mfi-tests
Add mFi tests
2016-02-08 19:07:48 -08:00
Paulus Schoutsen f222340c8e Merge pull request #1174 from balloob/wink-light
Wink light to inherit from light
2016-02-08 19:05:39 -08:00
Dan Smith b17df44402 Add tests for mFi switches 2016-02-09 03:03:26 +00:00
Dan Smith 895ddc8433 Add tests for mFi sensors
Note that some of the indirection here is so that I can reuse
a few things for mFi switch tests to follow.
2016-02-09 02:59:34 +00:00
Per Sandström d867d26612 Merge pull request #1168 from persandstrom/sigterm_handler
fix sigterm crash
2016-02-08 19:27:26 +01:00
Per SandstrÃom 564e328698 fix sigterm crash 2016-02-08 18:09:46 +00:00
Paulus Schoutsen 160b811ddf Wink light to inherit from light 2016-02-08 08:53:22 -08:00
Paulus Schoutsen 2e164e519a Merge pull request #1160 from stjohnjohnson/live-camera-stream
Fixes #1062 - If supported, directly stream the MJPEG from the remote camera
2016-02-07 14:43:00 -08:00
Paulus Schoutsen 779188ad27 Merge pull request #1167 from kk7ds/mfi-improvements
Mfi improvements
2016-02-07 14:40:47 -08:00
Paulus Schoutsen 3f6349d663 Merge pull request #1170 from balloob/fix_vera_dimmer_bug
Refactor VeraLight to inherit from Light, rather then VeraSwitch.
2016-02-07 14:39:16 -08:00
pavoni ac0dc10377 Refactor VeraLight to inherit from Light, rather then VeraSwitch. 2016-02-07 21:45:15 +00:00
Dan Smith 0a7db98b0e Round mFi sensor values to reasonable levels of precision
Most of the mFi sensors are able to reasonably provide accurate
readings to a tenth of a unit or so. This patch rounds them for
better display in the UI. Normally, I would expect this to be a view
action instead of altering the actual data emitted, but since these
values are reasonable for sensor precision, we're not really losing
anything. I followed the model from the openweathermap component, which
rounds for readability in the backend.
2016-02-07 20:51:00 +00:00
Dan Smith 8f690ff077 Add support for mPort input sensors
The mPort device has input pins that can be configured as digital or
analog inputs. We should support those as sensors.
2016-02-07 20:49:02 +00:00
Dan Smith 951fa603ff Support mPort voltage output switch types
An mPort device has a voltage output port that, if configured,
we should support like a switch.
2016-02-07 20:48:57 +00:00
Dan Smith c113997609 Tweak mFi switch behavior to avoid false states
When we update the mFi server for the state of a switch, the new
state is not always reported immediately if we update right after
the action (the server is not RESTful).

This patch adds some internal target-state handling to report the
desired state on the next poll, allowing any subsequent polls to
override that state.

Also, bump the version requirement for mficlient to 0.2.2 to absorb
a bug fix.
2016-02-07 20:48:51 +00:00
St. John Johnson f700635445 Add support for mjpeg component to proxy it's own stream 2016-02-07 12:02:52 -08:00
Paulus Schoutsen d49fae86e4 Update frontend with entity picture fix 2016-02-07 11:33:01 -08:00
Paulus Schoutsen 64611ab2be Merge pull request #1157 from balloob/device-state-attributes
Clean up state_attributes vs device_state_attributes
2016-02-07 10:16:34 -08:00
Paulus Schoutsen 27dc2f61fb Merge pull request #1163 from balloob/migration-5-fix
Fix recorder migration 5
2016-02-07 10:16:24 -08:00
Paulus Schoutsen cd25c8f72d Clean up some query stuff 2016-02-07 10:07:27 -08:00
Paulus Schoutsen 9ad1d290af Fix migration 5 2016-02-07 10:07:08 -08:00
Fabian Affolter 90ef81d8d5 Merge pull request #1161 from fabaff/bitcoin
Add icon
2016-02-07 15:37:40 +01:00
Fabian Affolter 02efe903ab Add icon 2016-02-07 12:43:02 +01:00
Fabian Affolter 0bb63bf3f0 Move configuration details to docs and add link 2016-02-07 12:36:29 +01:00
Fabian Affolter e23db5d972 Some small changes 2016-02-07 12:03:01 +01:00
Fabian Affolter e311f89056 Move details to docs and update docstrings/comments 2016-02-07 12:00:35 +01:00
Fabian Affolter 757946293e Move configuration details to docs 2016-02-07 11:52:17 +01:00
Paulus Schoutsen 98c6e56ea4 Merge pull request #1159 from nickwaring/proximity
Proximity component
2016-02-07 00:54:47 -08:00
Nick Waring cd0cef6403 Component to track the proximity of devices to a zone 2016-02-07 08:52:32 +00:00
Nick Waring 0d2891ebcc Test file for the proximity component 2016-02-07 08:51:21 +00:00
Paulus Schoutsen fb6aded2e1 Update frontend with new dependencies 2016-02-07 00:15:09 -08:00
Paulus Schoutsen 8b7cfc831d Merge pull request #1133 from balloob/cast-fix
Fix Chromecast discovery
2016-02-06 23:06:23 -08:00
Paulus Schoutsen 987be65d55 Update frontend to support default_view 2016-02-06 23:01:44 -08:00
Paulus Schoutsen f08b77dc4c Clean up state_attributes vs device_state_attributes 2016-02-06 22:34:24 -08:00
Paulus Schoutsen 681b84e1bd Update frontend with FF url-sync fix 2016-02-06 22:07:30 -08:00
Paulus Schoutsen 4103d7463b Merge pull request #1155 from balloob/fix-kb-interrupt
Fix Ctrl+C with Subprocesses
2016-02-06 20:12:27 -08:00
Paulus Schoutsen 428750eeda Merge pull request #1151 from kk7ds/add-mfi
Add support for Ubiquiti mFi sensors and switches
2016-02-06 20:11:53 -08:00
Paulus Schoutsen 0ae36e1d28 Merge pull request #1140 from kk7ds/add-nx584-alarm
Add Caddx/GE/Interlogix NetworX alarm panel support
2016-02-06 20:09:25 -08:00
Philip Lundrigan d2e8721918 Merge pull request #1124 from philipbl/fix_influx
Fix InfluxDB field type conflict
2016-02-06 20:49:20 -07:00
Philip Lundrigan bbdc196127 Use entity_id attribute 2016-02-06 20:33:43 -07:00
Paulus Schoutsen a417156d84 Merge pull request #1154 from balloob/fix-time-utils
Fixed time zone conversion with no TZ specified
2016-02-06 19:10:14 -08:00
Dan Smith 3575ddb6ef Add Caddx/GE/Interlogix NetworX alarm panel support
This adds support for NetworX-based alarm panels and should work for
any such panel equipped with a NX584 serial interface module. This
includes NX-4/6/8/8E, where the NX8E has this interface built-in.

It requires the pynx584 module, and requires running the server
component somewhere that has connectivity to the panel via serial,
which may include a serial-over-lan connection.
2016-02-07 03:06:58 +00:00
Dan Smith ffc4822f50 Add support for Ubiquiti mFi switchable devices
This adds support for mFi devices that are swichable,
such as the mFi Outlet device.
2016-02-07 03:02:49 +00:00
Dan Smith a147304be9 Add support for Ubiquiti mFi sensors
This adds support for sensors based on Ubiquiti's mFi platform.
All ports/sensors are detected from the mFi controller and exposed.
2016-02-07 02:58:04 +00:00
Ryan Kraus a001780afb Fix Ctrl+C with Subprocesses
Added KeyboardInterrupt handling back to block_till_stopped method.
This is because Keyboard Interrupts are sent to both the parent and
child process in no particular order so both need to handle the
interrupt.
2016-02-06 21:50:06 -05:00
Ryan Kraus 7a00bf8696 Fixed time zone conversion with no TZ specified
Using .replace to set the current time zone appears to not handle
things correctly. The proper way to do this is apparently .localize.
2016-02-06 21:31:07 -05:00
Paulus Schoutsen 7eef831ff3 Merge pull request #1141 from kk7ds/add-uvc-cameras
Add Ubiquiti Unifi Video Camera support
2016-02-06 18:21:44 -08:00
Paulus Schoutsen 9fde97efed Merge pull request #1152 from balloob/bump-pyvera-version
Bump pyvera, pywemo, add available for wemo
2016-02-06 16:43:42 -08:00
MartinHjelmare d773ad1ecb Fix mysensors version errors
* The application of the version check in unit_of_measurement was
	messed up after the last refactor. Fix that again.
* An error could occur in device_state_attributes if there was a
	mismatch between used value_type in the device and mysensors
	version in config. Add try... except to handle that.

Bump pyvera version.

Bump pywemo version.

Add unavailable status before properly initialised for maker and insight.
2016-02-07 00:28:12 +00:00
Dan Smith cab1100a51 Add Ubiquiti Unifi Video Camera support
This adds support for Ubiquiti's UniFi Video cameras via their
NVR device (or service). By configuring just the address of the
NVR and a valid API key, all cameras are discovered and enabled,
including direct-to-camera image snapshot-based video support.
2016-02-06 22:57:44 +00:00
Dan Smith 3616d7a7ea Fix alarm service handler state updates
This changes the service handler dispatch code to always
call update_ha_state(), and cleans up the alarm platforms that
were calling it themselves.
2016-02-06 21:11:28 +00:00
Paulus Schoutsen d38ad57b7d Merge pull request #1142 from kk7ds/add-honeywellus-thermostat
Add Honeywell US thermostat support
2016-02-06 13:05:53 -08:00
Paulus Schoutsen b3e966665a Merge pull request #1150 from bradsk88/master
Updating to python-wink 0.5.0
2016-02-06 13:04:42 -08:00
bradsk88 6e69737e88 Updating to python-wink 0.5.0
Major bugfix.  ``` get_bulbs ``` and similar methods were always returning empty lists.

Better unit tests have been added to https://github.com/bradsk88/python-wink to avoid this regression.
2016-02-06 14:22:46 -06:00
Dan Smith 062fe79b3f Add Honeywell US thermostat support
This adds support for the US variant of the Honeywell connected
thermostat. The interface is super simple, so this doesn't add
any external dependencies. It supports basic temperature, setpoint,
and control.

Issue #998 notes that the existing honeywell module doesn't work
for US models, which is because they are totally different. In order
to indicate to the honeywell platform module that the thermostat
is a US-type, we key off of whether or not the thermostat id is
provided. This is something that US people have (and require to
identify one of potentially multiple thermostats in their account)
and EU people will not.
2016-02-06 20:14:40 +00:00
Paulus Schoutsen af0a44d976 Merge pull request #1144 from MartinHjelmare/fix-mysensors-errors
Fix mysensors version errors
2016-02-06 11:35:49 -08:00
Paulus Schoutsen 43613f000d Merge pull request #1147 from balloob/debug-flag
Fix Interactive Debuggers
2016-02-06 09:56:12 -08:00
Paulus Schoutsen dde80850a6 Merge pull request #1112 from balloob/switch-template
First cut of switch.template
2016-02-06 09:12:13 -08:00
Ryan Kraus 11a2b8888b Fixes for issue #1114
1. Moved RESTART_EXIT_CODE to constants so it can safely be used by
__main__.py.
2. Allowed __main__/main to return the desired exit code.
3. Forwarded the child processes exit code to the parent process to be
duplicated.
4. Added —debug flag to pass command to force Home Assistant to run in
only one process. A warning is printed to STDERR to indicate HASS is in
debug mode. Another is printed if HASS requests a restart in debug
mode. A restart request in debug mode will quit.
5. Added an argument to __main__/main/setup_and_run_hass to indicate
that it is running in the top process. This tells it to return the exit
code rather than exiting.
2016-02-06 09:48:36 -05:00
MartinHjelmare b700ec4faa Fix mysensors version errors
* The application of the version check in unit_of_measurement was
	messed up after the last refactor. Fix that again.
* An error could occur in device_state_attributes if there was a
	mismatch between used value_type in the device and mysensors
	version in config. Add try... except to handle that.
2016-02-06 10:57:42 +01:00
Fabian Affolter 614034d196 Update docstrings 2016-02-06 08:23:30 +01:00
Paulus Schoutsen c9d145cb13 Merge pull request #1120 from haraldnagel/dev
Add BloomSky weather station support
2016-02-05 20:06:53 -08:00
Harald Nagel b6a32098d1 Add BloomSky weather station support 2016-02-06 01:37:32 +00:00
Flyte 4cf85294db Add template support to mqtt.publish service payload. 2016-02-05 21:47:27 +00:00
Eric Rolf 6fc68e9c8a Forgot to refactor demo. 2016-02-05 15:16:56 -05:00
Eric Rolf ec88733b57 Refactor Method Name For Open and Close. 2016-02-05 15:10:53 -05:00
Eric Rolf 7ef2075520 Updated requirements_all.txt 2016-02-05 14:20:06 -05:00
Eric Rolf fbd0dbf8ee Wink Garage Door Support 2016-02-05 12:53:57 -05:00
Paulus Schoutsen 2ba237eac8 Merge pull request #1134 from fabaff/icons
Add icon
2016-02-05 07:33:31 -08:00
Fabian Affolter 6bf4532608 Add icon 2016-02-05 13:08:17 +01:00
pavoni 2622cf2e53 Use available, remove state, improve true,false tests. 2016-02-05 11:18:50 +00:00
Paulus Schoutsen a5db23afa4 Mock util.location by default 2016-02-04 22:26:02 -08:00
Paulus Schoutsen 2c4166b5f2 Update splunk.py 2016-02-04 22:06:27 -08:00
Paulus Schoutsen 8be9aaba4f Fix Chromecast discovery 2016-02-04 21:36:37 -08:00
Paulus Schoutsen 1b16d76c40 Merge pull request #1128 from miniconfig/splunk
Added a new component to log state changes to a Splunk instance using…
2016-02-04 20:58:12 -08:00
Paulus Schoutsen 1a6539ad41 Merge pull request #1110 from lukas-hetzenecker/feature-zwave-poll-and-scene
Z-Wave: Scene activation & Polling
2016-02-04 20:51:52 -08:00
Paulus Schoutsen 96066e94ab Merge pull request #1122 from molobrakos/tellduslive
reworked telldus live support
2016-02-04 20:46:23 -08:00
nkgilley@gmail.com 78e758925b remove commented lines. 2016-02-04 18:42:38 -05:00
nkgilley@gmail.com 19fc48f4a0 use raw regex 2016-02-04 18:39:09 -05:00
nkgilley@gmail.com d469970e5a Speedtest.net component 2016-02-04 18:21:37 -05:00
Erik 50a9b3a7c0 reworked telldus live support 2016-02-05 00:07:12 +01:00
miniconfig ab837f9070 Added a new component to log state changes to a Splunk instance using the HTTP Event Collector 2016-02-04 16:13:55 -05:00
Paulus Schoutsen 6149e509c3 Merge pull request #1127 from molobrakos/timedate
provide default icon
2016-02-04 13:04:09 -08:00
Erik f3b74079e0 provide default icon 2016-02-04 21:55:22 +01:00
Philip Lundrigan c580953bd8 Fix MQTT sensor 2016-02-04 12:38:48 -07:00
Philip Lundrigan fc3741911c Fix problem with field type conflict in influxdb 2016-02-04 12:38:48 -07:00
Lukas Hetzenecker 2589e78e84 Z-Wave: This small refactor adds the following features:
* The poll interval got fixed and the poll intensity gets configurable in the settings
* Activated scenes now fire an event
2016-02-04 19:54:43 +01:00
pavoni ced380f0cd Remove unneeded entity_id check and blank lines. 2016-02-04 17:24:38 +00:00
Paulus Schoutsen a33f1c61e5 Merge pull request #1108 from lukas-hetzenecker/regression-zwave-light
Z-Wave Regression: Fix state attributes of lights and switches.
2016-02-03 21:25:13 -08:00
Lukas Hetzenecker 8cf5ca0ba8 Regression: The device specific attributes of ZWave devices got lost.
Light and ZWaveDeviceEntity both have overwritten the property state_attributes
This includes the device specific attributes in the state_attributes again
2016-02-04 02:12:33 +01:00
pavoni b20d3f8b3a Update docstrings. 2016-02-03 23:23:19 +00:00
Paulus Schoutsen 1f34b3586e Merge pull request #1117 from philipbl/influx_ssl
Add ability to specify SSL for InfluxDB connection
2016-02-03 13:33:08 -08:00
Paulus Schoutsen 37dadd1ae0 Merge pull request #974 from sdague/domains
Add recording of domain to state tables
2016-02-03 12:26:31 -08:00
Paulus Schoutsen fac8d4b969 Merge pull request #1115 from sdague/test
add pytest-timeout to test runs
2016-02-03 12:24:35 -08:00
Sean Dague efcba8f1ca add pytest-timeout to test runs
This adds a default 30 second timeout on every test method so that
deadlocks or broken threads are move obvious in travis. It also passes
-v by default to make things a little more verbose on where things
fail when they are failing.
2016-02-03 15:13:30 -05:00
Sean Dague abc253c4c5 implement get_significant_states
This adds a new function to history module which returns significant
states. For most domains this is the list of state changes. For the
thermostat domain this also includes attribute changes, so that
changes in the current_temperature are exposed to the graphing layer.

Closes #881
2016-02-03 15:05:43 -05:00
Sean Dague 3d00735341 Add recording of domain to state tables
Some domains, like thermostat, need all state records, not just state
change ones, to provide accurate graphs. This introduces a new db
migration which adds a 'domain' column to all states so that is a fast
query.

Indexes were added to help with query performance.

This includes a data migration which post-date populates domain. On
large HA dbs this might take real time, as it has to touch every state
row. 100 MB db (91k states) updated in a couple of seconds on my
reasonably fast server. Be forewarned.

This is part of bug #881
2016-02-03 15:05:43 -05:00
Paulus Schoutsen ce75c590b1 Merge pull request #1109 from lukas-hetzenecker/bugfix-zwave-fibaro-wall-plug
Z-Wave: Bugfix for Fibaro Wall Plug component
2016-02-03 08:34:06 -08:00
pavoni 6e6c3c5cd5 Tidy. 2016-02-03 14:30:58 +00:00
pavoni 5521096c02 Add actions. 2016-02-03 14:29:25 +00:00
Fabian Affolter 356013118d Update docstrings 2016-02-03 15:13:53 +01:00
pavoni 9a9dbcfaea Refactor, support template logic values, add tests. 2016-02-03 13:16:13 +00:00
Lukas Hetzenecker 1c33e01b99 styleguide fix 2016-02-03 13:03:01 +01:00
Lukas Hetzenecker d09837fef6 Zwave: This is a bugfix for the Fibaro Wall Plug component
As discussed here ( https://www.domoticz.com/forum/viewtopic.php?f=6&t=5661 ) this components reports two different power consumption values.
Unfortunately only one of them is correct. Both of them map to the exactly same object id.
This bugfix gets rid of the incorrect one.
2016-02-03 12:44:11 +01:00
Paulus Schoutsen f5e736d271 Merge pull request #1105 from balloob/disable-location-in-tests
Disable location queries
2016-02-02 21:38:20 -08:00
Paulus Schoutsen 61630783f1 Disable location queries 2016-02-02 21:33:59 -08:00
Paulus Schoutsen 077797ac4f Merge pull request #1086 from carlosmgr/dev
update SSH for aruba device tracker
2016-02-02 18:33:49 -08:00
Paulus Schoutsen b14f7f7ed0 Merge pull request #1099 from balloob/sensor_template_startup_error
Sensor template startup error
2016-02-02 18:32:09 -08:00
carlosmgr 3d695405b7 pep8 2 time 2016-02-03 00:03:50 +00:00
carlosmgr 55932b048e fix pep8 2016-02-02 23:54:32 +00:00
Fabian Affolter b19fbd8e72 Update docstrings 2016-02-03 00:35:53 +01:00
Fabian Affolter 635369ad65 Update docstrings 2016-02-03 00:23:59 +01:00
Fabian Affolter bd8881cbe1 Add icon 2016-02-03 00:12:01 +01:00
Fabian Affolter 847e92f57a Update docstrings 2016-02-03 00:01:26 +01:00
Fabian Affolter 5cea8fda9f Update link to docs and add docstrings 2016-02-02 23:55:44 +01:00
carlosmgr 7f87df20c2 ssh aruba 2016-02-02 22:40:04 +00:00
carlosmgr e91c8e4143 ssh aruba.py
req pexpect
2016-02-02 21:49:11 +00:00
carlosmgr cd00ff8b56 Merge remote-tracking branch 'upstream/dev' into dev 2016-02-02 20:07:31 +00:00
Philip Lundrigan 018329b12b Add ability to specify ssl 2016-02-02 12:58:38 -07:00
pavoni a955f3db08 WIP commit - template state working, on / off still to do. 2016-02-02 19:25:17 +00:00
pavoni d344defc7e Switch to warning. 2016-02-02 16:26:17 +00:00
Paulus Schoutsen 2da422fd77 Merge pull request #1098 from balloob/media-player-test
Clean up and test media player
2016-02-02 08:09:40 -08:00
pavoni 93a38d39ef Add test for missimng attributes. 2016-02-02 14:15:06 +00:00
pavoni 3aad223c95 Change to warning if attributes missing. 2016-02-02 14:14:29 +00:00
Ryan Kraus 1a5d18fd66 Merge pull request #1012 from balloob/restart-service
Restart service
2016-02-02 07:15:40 -05:00
Paulus Schoutsen e7e540d4bb Clean up and test media player 2016-02-02 00:31:36 -08:00
Paulus Schoutsen 35613d7fbf Merge pull request #1097 from molobrakos/add-test
add test for unsafe yaml
2016-02-02 00:22:19 -08:00
Erik 00d1cab091 add test for unsafe yaml 2016-02-02 08:41:18 +01:00
Paulus Schoutsen 26efaa91a3 Merge pull request #1090 from balloob/fix_sensor_template_entity_id
Fix bug in sensor.template entity_id
2016-02-01 21:21:57 -08:00
Paulus Schoutsen 3c37ecc477 Merge pull request #1091 from TangoAlpha/dev
Improvements to device detection
2016-02-01 21:21:15 -08:00
Paulus Schoutsen 274aaabd93 Merge pull request #1094 from balloob/cast-multi-room
No longer ignore ports for Chromecasts
2016-02-01 21:16:50 -08:00
Paulus Schoutsen c8bfd27182 No longer ignore ports for Chromecasts 2016-02-01 21:07:33 -08:00
Tim 08ab7dba2c Fix whitespace 2016-02-02 00:21:15 +00:00
Tim 54cc35d729 Merge branch 'dev' of https://github.com/TangoAlpha/home-assistant into dev 2016-02-02 00:17:03 +00:00
Tim 031e7a4013 New liffylights release improves device detection
Increase device polling to 30 seconds
2016-02-02 00:15:38 +00:00
Paulus Schoutsen 41165695f0 Merge pull request #1071 from deisi/onewireupdate
Little improvement for the onewire platform
2016-02-01 15:26:22 -08:00
pavoni 9c33af60f2 Fix unreachable code! 2016-02-01 18:38:11 +00:00
pavoni 7c1241c1f8 Add another test, revise another. Improve coverage. 2016-02-01 18:30:39 +00:00
Tim 9caa4752a4 New liffylights release improves device detection
Increase device polling to 30 seconds
2016-02-01 18:29:43 +00:00
Malte Deiseroth cb2e75befd removed trailing whitespace 2016-02-01 19:24:08 +01:00
pavoni d54e10e54a Improve test coverage of error conditions. 2016-02-01 18:18:51 +00:00
pavoni 95748a6880 Generate entity id correctly, was using friendly_name. 2016-02-01 17:45:18 +00:00
Paulus Schoutsen 10a41a22dc Merge pull request #1084 from molobrakos/tellduslive
handle situation where no name is set yet for the sensor
2016-02-01 08:55:39 -08:00
Erik ac0b6ca50c handle situation where no name is set yet for the sensor 2016-02-01 17:42:08 +01:00
Paulus Schoutsen a0f6f3ac22 Merge pull request #1085 from molobrakos/yaml_safe_load
use yaml safe loader
2016-02-01 08:15:32 -08:00
Paulus Schoutsen d9aff0c76d Merge pull request #1089 from balloob/yr
Added and fixed yr tests
2016-02-01 08:11:18 -08:00
Daniel 5005b20122 Added and fixed yr tests 2016-02-01 15:50:17 +01:00
carlosmgr b3ef2bd2d9 Update aruba.py 2016-02-01 12:23:20 +00:00
Fabian Affolter e29a2fa45a Fix typo 2016-02-01 11:49:44 +01:00
Fabian Affolter 395743005a Add link to docs, new docstrings, and update docstrings 2016-02-01 11:47:09 +01:00
Fabian Affolter 8de56bc8e2 Fix docstrings 2016-02-01 11:31:27 +01:00
Paulus Schoutsen 79b6269aa2 Merge pull request #1083 from balloob/fix-requirements
Fix gen_requirements_all
2016-02-01 00:00:51 -08:00
Paulus Schoutsen 525b206e1b Fix gen_requirements_all 2016-01-31 23:55:02 -08:00
Paulus Schoutsen 2f4e40db27 Merge pull request #1076 from MartinHjelmare/add-entity-online-prop
Fix 'unavailable' entity
2016-01-31 17:26:12 -08:00
MartinHjelmare 38c9f7a37a Fix 'unavailable' entity
* Report friendly_name, icon and customized attributes for 'unavailable'
  entities.
2016-02-01 02:16:56 +01:00
Paulus Schoutsen c725f7883a Merge pull request #1063 from stefan-jonasson/samsungtv
Added a new media_player platform for controlling Samsung TVs
2016-01-31 16:57:11 -08:00
Paulus Schoutsen 10f79ab45d Merge pull request #1073 from MartinHjelmare/add-entity-online-prop
Add new 'available' property to entity.py
2016-01-31 15:59:28 -08:00
MartinHjelmare 455593017d Add new 'available' property to entity.py
* Add 'available' property. Return True by default.
* Use new property in update_ha_state(). If available is False, set
  state to 'unavailable', through constant.
* Add STATE_UNAVAILABLE constant 'unavailable' in const.py.
* Fix docstrings in entity.py and const.py, according to PEP257.
  Ignore D203 and D105.
2016-01-31 23:58:19 +01:00
Malte Deiseroth d6b19aae48 - check for reasonable temperature values
- round temperature to one digit
2016-01-31 22:56:48 +01:00
Paulus Schoutsen 6519333e1d Merge pull request #1070 from balloob/input-select
Add input_select component
2016-01-31 13:44:32 -08:00
Paulus Schoutsen 41919e7339 Update frontend for input_select 2016-01-31 13:39:50 -08:00
Stefan Jonasson 1c10f218de Fixed duplicate import statements and made use of the config_helper 2016-01-31 22:17:00 +01:00
Paulus Schoutsen 96710ad410 Add input_select component 2016-01-31 12:52:51 -08:00
Paulus Schoutsen c95c3d9198 Update frontend with weblink support 2016-01-31 12:00:45 -08:00
Stefan Jonasson 5719743ec7 Fixed .coveragerc and requirements_all.txt 2016-01-31 20:02:51 +01:00
Per Sandström e2e8d4276f Merge pull request #1069 from persandstrom/vsure0.5.0
vsure 0.5.0
2016-01-31 19:33:09 +01:00
Per Sandström 3f03fefd35 vsure 0.5.0 2016-01-31 19:23:53 +01:00
Stefan Jonasson 2dab815f90 Fixes imports, styles and other misstates 2016-01-31 19:12:00 +01:00
Paulus Schoutsen 6a4b63f807 Merge pull request #1056 from flavio/scsgate
Add support for the SCSGate device
2016-01-31 09:34:07 -08:00
Flavio Castelli 8eef978241 Add support for the SCSGate device
Support the SCSGate device. This will allow home-assistant to interact
with BTicino/Legrand MyHome system.

Signed-off-by: Flavio Castelli <flavio@castelli.me>
2016-01-31 18:30:43 +01:00
Paulus Schoutsen 1789a08d21 Merge pull request #1065 from kennedyshead/weblink
Weblink component
2016-01-31 09:20:17 -08:00
magnusknutas de4dab74b1 Adding weblink component
Adding weblink component tests
2016-01-31 18:13:04 +01:00
Paulus Schoutsen 16b1529d14 Merge pull request #1059 from balloob/entity-component-enhancements
Add tests and custom interval for entity component
2016-01-31 09:03:55 -08:00
Paulus Schoutsen 0b8e097705 Remove unused environment util 2016-01-31 08:58:30 -08:00
Paulus Schoutsen b21be63220 Merge pull request #1066 from TangoAlpha/dev
Update for new liffylights release
2016-01-31 08:32:51 -08:00
Tim e6a8746dba Update requirements_all 2016-01-31 15:07:06 +00:00
Tim 1974eda51d Update for new liffylights release
Fix incorrect packet timeout/ack code causing flooding when no bulbs were online, which consumed all WorkerPool threads
2016-01-31 13:31:12 +00:00
Stefan Jonasson bd475f5db1 Added a new media_player platform for controlling Samsung TVs with a lan interface.
Configured like this
 media_player:
  platform: samsungtv
  host: <IP OF TV>
  name: <Name of the tv>
2016-01-31 10:06:39 +01:00
Paulus Schoutsen fce8815ab4 Support custom interval for platforms 2016-01-31 01:01:23 -08:00
Paulus Schoutsen 90e17fc77f Add tests for entity component 2016-01-31 01:01:23 -08:00
Paulus Schoutsen 6418634f3a Merge pull request #1049 from balloob/new-tests
Adding more unit tests
2016-01-30 23:32:34 -08:00
Ryan Kraus a230d00ed0 Added test for Introduction component
This test may seem useless, but it is good to ensure that default
components don’t ever crash HASS.
2016-01-30 22:50:56 -05:00
Ryan Kraus 5fdbe5fd9a More tests for Binary Command Sensor
1. Added a test for detecting STATE_OFF
2. Fixed tests for detecting STATE_ON
2016-01-30 22:41:29 -05:00
Ryan Kraus 283d621e90 Added tests for Binary Command Sensor 2016-01-30 22:32:25 -05:00
Ryan Kraus 2d0004f46a Another test for for command sensor
Added a test for command sensors with bad configurations.
2016-01-30 22:16:22 -05:00
Ryan Kraus 6a08f14120 Additional tests for Command Sensor.
1. Moved template testing out of main test.
2. Added test for bad command.
2016-01-30 22:13:42 -05:00
Ryan Kraus 97e867052d Added tests for command sensor
Added tests to create and check basic functionality of command sensor.
2016-01-30 22:01:10 -05:00
Ryan Kraus 2651021461 Added test for entity customization
Added test for entity customization from configuration. Processes a
sample configuration to hide an entity, creates the entity, updates ha
state, and then verifies customization made it through.
2016-01-30 21:27:00 -05:00
Erik 4b253d17ba use yaml safe loader 2016-01-31 00:46:08 +01:00
Paulus Schoutsen b7722ec452 Allow usage of words domain, service, call_id in service data 2016-01-30 15:18:26 -08:00
magnusknutas fd6086a5d6 Testing logbook service 2016-01-30 15:18:26 -08:00
Paulus Schoutsen 3e35bc06fc Merge pull request #1055 from TangoAlpha/dev
Update to new release of liffylights
2016-01-30 12:56:22 -08:00
Tim f76dee8a05 Update to new release of liffylights 2016-01-30 20:48:57 +00:00
Ryan Kraus 56ac4281c7 Better tear down of util/package tests
Explicitly removed temp directory at the end of util/package unit tests.
2016-01-30 14:39:17 -05:00
Ryan Kraus b8e149fe7d Cleaned up universal MP tests
Changed all assertEquals in universal media player tests to have
assumed value first.
2016-01-30 14:38:27 -05:00
Ryan Kraus 4a8f55e630 Revised package util tests
The package util tests were revised to pull the external library
pyhelloworld3 from an internal source rather than external. This speeds
up tests, makes tests more reliable, and removes dependency on internet
connection.
2016-01-30 14:08:32 -05:00
Ryan Kraus de61bcb80e Additional testing for logger component
Added an additional test for the logger component the validates the
filtering logic of the filters that were created during setup.
2016-01-30 13:23:35 -05:00
Ryan Kraus 4cc9606bcc Added test for logger component. 2016-01-30 13:03:46 -05:00
Ryan Kraus 8ac763c6f6 Added test for universal mp service routing.
Added tests to ensure that the Universal Media Player is routing
service calls correctly.
2016-01-30 12:26:28 -05:00
Ryan Kraus 6a75b524cb Removed unused private method from universal media player
The universal media player contained a private method that was replaced
by the update method. It was meant to be removed and wasn’t. This
commit removed that method.
2016-01-30 11:57:46 -05:00
Ryan Kraus c1d057407b Fixed typo in universal media player test. 2016-01-30 11:53:15 -05:00
Ryan Kraus c396dbb570 Added tests to check setup and config of universal media player. 2016-01-30 07:18:37 -05:00
Ryan Kraus 0631f5c59d Added tests for package utilities 2016-01-30 06:44:22 -05:00
Paulus Schoutsen 10f9c049bb Version bump to 0.13.0.dev0 2016-01-29 22:38:01 -08:00
Ryan Kraus 106c53abf1 Revised HASS Core test
Changed the HASS Core test that tested KeyboardInterrupt handling to
now test SIGTERM handling. KeyboardInterrupts are no longer handled in
the HASS application process as they are handled in the HASS parent
process. SIGTERM is the proper way to now stop HASS.
2016-01-29 22:42:39 -05:00
Ryan Kraus b56369855a Cleaned up restart handling in __main__.py
1. Fixed logged message about SIGTERM binding failure.
2. Set to only restart HASS with an exit code of 100.
3. Fixed typo in comment.
2016-01-29 22:11:11 -05:00
Ryan Kraus a41b66bb94 Cleaned up block_till_stop in core.py
1. Removed handling of KeyboardInterrupt. This will no longer happen
now that HASS is run in a subprocess. The KeyboardInterrupt will not be
sent to the parent process which will send a SIGTERM to the HASS
process.
2. Fixed logger warning about not being able to bind to SIGTERM.
3. Removed check for Windows OSs when binding to SIGTERM. This check
was originally put in place when HASS was binding to SIGQUIT. SIGTERM
exists in NT OSs, so the check is no longer required.
3. Now returning exit code of 100 when requesting a restart. This will
allow the parent process to only restart HASS if it is specifically
requested and not just on any encountered crash.
2016-01-29 22:02:39 -05:00
Ryan Kraus 3534c975f3 Added missing CONF_ICON constant 2016-01-26 22:46:01 -05:00
Ryan Kraus 519abbbfa2 Better handling of second KeyboardInterrupt
Now the second KeyboardInterrupt will be cleanly handled by the parent
process.
2016-01-26 22:41:57 -05:00
Ryan Kraus b596fa33d6 Implemented restart service
Implemented an OS and environment safe restart service. This works by
running Home Assistant in a child process. If the child process
terminates with an exit code > 0, HASS is restarted. SIGTERM and
KeyboardInterrupts to the parent process are forwarded to the child
process. KeyboardInterrupts will only be forwarded once. The second
KeyboardInterrupt will be handled by the parent.
2016-01-26 22:39:59 -05:00
169 changed files with 11702 additions and 4686 deletions
+16 -4
View File
@@ -6,10 +6,17 @@ omit =
# omit pieces of code that rely on external devices being present
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
homeassistant/components/apcupsd.py
homeassistant/components/*/apcupsd.py
homeassistant/components/bloomsky.py
homeassistant/components/*/bloomsky.py
homeassistant/components/insteon_hub.py
homeassistant/components/*/insteon_hub.py
@@ -53,6 +60,9 @@ omit =
homeassistant/components/rpi_gpio.py
homeassistant/components/*/rpi_gpio.py
homeassistant/components/scsgate.py
homeassistant/components/*/scsgate.py
homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/rest.py
homeassistant/components/browser.py
@@ -73,9 +83,8 @@ omit =
homeassistant/components/device_tracker/ubus.py
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/garage_door/wink.py
homeassistant/components/ifttt.py
homeassistant/components/statsd.py
homeassistant/components/influxdb.py
homeassistant/components/keyboard.py
homeassistant/components/light/blinksticklight.py
homeassistant/components/light/hue.py
@@ -89,21 +98,24 @@ omit =
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/plex.py
homeassistant/components/media_player/samsungtv.py
homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/googlevoice.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/pushbullet.py
homeassistant/components/notify/pushetta.py
homeassistant/components/notify/pushover.py
homeassistant/components/notify/rest.py
homeassistant/components/notify/slack.py
homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py
homeassistant/components/notify/googlevoice.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/cpuspeed.py
@@ -118,6 +130,7 @@ omit =
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/temper.py
@@ -140,7 +153,6 @@ omit =
homeassistant/components/thermostat/proliphix.py
homeassistant/components/thermostat/radiotherm.py
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
+102 -32
View File
@@ -1,13 +1,18 @@
""" Starts home assistant. """
from __future__ import print_function
from multiprocessing import Process
import signal
import sys
import threading
import os
import argparse
import time
from homeassistant import bootstrap
import homeassistant.config as config_util
from homeassistant.const import __version__, EVENT_HOMEASSISTANT_START
from homeassistant.const import (__version__, EVENT_HOMEASSISTANT_START,
RESTART_EXIT_CODE)
def validate_python():
@@ -73,6 +78,11 @@ def get_arguments():
'--demo-mode',
action='store_true',
help='Start Home Assistant in demo mode')
parser.add_argument(
'--debug',
action='store_true',
help='Start Home Assistant in debug mode. Runs in single process to '
'enable use of interactive debuggers.')
parser.add_argument(
'--open-ui',
action='store_true',
@@ -204,35 +214,11 @@ def uninstall_osx():
print("Home Assistant has been uninstalled.")
def main():
""" Starts Home Assistant. """
validate_python()
args = get_arguments()
config_dir = os.path.join(os.getcwd(), args.config)
ensure_config_path(config_dir)
# os x launchd functions
if args.install_osx:
install_osx()
return
if args.uninstall_osx:
uninstall_osx()
return
if args.restart_osx:
uninstall_osx()
install_osx()
return
# daemon functions
if args.pid_file:
check_pid(args.pid_file)
if args.daemon:
daemonize()
if args.pid_file:
write_pid(args.pid_file)
def setup_and_run_hass(config_dir, args, top_process=False):
"""
Setup HASS and run. Block until stopped. Will assume it is running in a
subprocess unless top_process is set to true.
"""
if args.demo_mode:
config = {
'frontend': {},
@@ -259,7 +245,91 @@ def main():
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser)
hass.start()
hass.block_till_stopped()
exit_code = int(hass.block_till_stopped())
if not top_process:
sys.exit(exit_code)
return exit_code
def run_hass_process(hass_proc):
""" Runs a child hass process. Returns True if it should be restarted. """
requested_stop = threading.Event()
hass_proc.daemon = True
def request_stop(*args):
""" request hass stop, *args is for signal handler callback """
requested_stop.set()
hass_proc.terminate()
try:
signal.signal(signal.SIGTERM, request_stop)
except ValueError:
print('Could not bind to SIGTERM. Are you running in a thread?')
hass_proc.start()
try:
hass_proc.join()
except KeyboardInterrupt:
request_stop()
try:
hass_proc.join()
except KeyboardInterrupt:
return False
return (not requested_stop.isSet() and
hass_proc.exitcode == RESTART_EXIT_CODE,
hass_proc.exitcode)
def main():
""" Starts Home Assistant. """
validate_python()
args = get_arguments()
config_dir = os.path.join(os.getcwd(), args.config)
ensure_config_path(config_dir)
# os x launchd functions
if args.install_osx:
install_osx()
return 0
if args.uninstall_osx:
uninstall_osx()
return 0
if args.restart_osx:
uninstall_osx()
# A small delay is needed on some systems to let the unload finish.
time.sleep(0.5)
install_osx()
return 0
# daemon functions
if args.pid_file:
check_pid(args.pid_file)
if args.daemon:
daemonize()
if args.pid_file:
write_pid(args.pid_file)
# Run hass in debug mode if requested
if args.debug:
sys.stderr.write('Running in debug mode. '
'Home Assistant will not be able to restart.\n')
exit_code = setup_and_run_hass(config_dir, args, top_process=True)
if exit_code == RESTART_EXIT_CODE:
sys.stderr.write('Home Assistant requested a '
'restart in debug mode.\n')
return exit_code
# Run hass as child process. Restart if necessary.
keep_running = True
while keep_running:
hass_proc = Process(target=setup_and_run_hass, args=(config_dir, args))
keep_running, exit_code = run_hass_process(hass_proc)
return exit_code
if __name__ == "__main__":
main()
sys.exit(main())
@@ -61,6 +61,8 @@ def setup(hass, config):
for alarm in target_alarms:
getattr(alarm, method)(code)
if alarm.should_poll:
alarm.update_ha_state(True)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
@@ -57,10 +57,12 @@ class AlarmDotCom(alarm.AlarmControlPanel):
@property
def should_poll(self):
""" No polling needed. """
return True
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
@@ -88,7 +90,6 @@ class AlarmDotCom(alarm.AlarmControlPanel):
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.disarm()
self.update_ha_state()
def alarm_arm_home(self, code=None):
""" Send arm home command. """
@@ -98,7 +99,6 @@ class AlarmDotCom(alarm.AlarmControlPanel):
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.arm_stay()
self.update_ha_state()
def alarm_arm_away(self, code=None):
""" Send arm away command. """
@@ -108,7 +108,6 @@ class AlarmDotCom(alarm.AlarmControlPanel):
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.arm_away()
self.update_ha_state()
def _validate_code(self, code, state):
""" Validate given code. """
@@ -0,0 +1,105 @@
"""
homeassistant.components.alarm_control_panel.nx584
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for NX584 alarm control panels.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.nx584/
"""
import logging
import requests
from homeassistant.const import (STATE_UNKNOWN, STATE_ALARM_DISARMED,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY)
import homeassistant.components.alarm_control_panel as alarm
REQUIREMENTS = ['pynx584==0.1']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup nx584. """
host = config.get('host', 'localhost:5007')
try:
add_devices([NX584Alarm(hass, host, config.get('name', 'NX584'))])
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to NX584: %s', str(ex))
return False
class NX584Alarm(alarm.AlarmControlPanel):
""" NX584-based alarm panel. """
def __init__(self, hass, host, name):
from nx584 import client
self._hass = hass
self._host = host
self._name = name
self._alarm = client.Client('http://%s' % host)
# Do an initial list operation so that we will try to actually
# talk to the API and trigger a requests exception for setup_platform()
# to catch
self._alarm.list_zones()
@property
def should_poll(self):
""" Polling needed. """
return True
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def code_format(self):
""" Characters if code is defined. """
return '[0-9]{4}([0-9]{2})?'
@property
def state(self):
""" Returns the state of the device. """
try:
part = self._alarm.list_partitions()[0]
zones = self._alarm.list_zones()
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to %(host)s: %(reason)s',
dict(host=self._host, reason=ex))
return STATE_UNKNOWN
except IndexError:
_LOGGER.error('nx584 reports no partitions')
return STATE_UNKNOWN
bypassed = False
for zone in zones:
if zone['bypassed']:
_LOGGER.debug('Zone %(zone)s is bypassed, '
'assuming HOME',
dict(zone=zone['number']))
bypassed = True
break
if not part['armed']:
return STATE_ALARM_DISARMED
elif bypassed:
return STATE_ALARM_ARMED_HOME
else:
return STATE_ALARM_ARMED_AWAY
def alarm_disarm(self, code=None):
""" Send disarm command. """
self._alarm.disarm(code)
def alarm_arm_home(self, code=None):
""" Send arm home command. """
self._alarm.arm('home')
def alarm_arm_away(self, code=None):
""" Send arm away command. """
self._alarm.arm('auto')
def alarm_trigger(self, code=None):
""" Alarm trigger command. """
raise NotImplementedError()
+84
View File
@@ -0,0 +1,84 @@
"""
homeassistant.components.apcupsd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sets up and provides access to the status output of APCUPSd via its Network
Information Server (NIS).
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/apcupsd/
"""
import logging
from datetime import timedelta
from homeassistant.util import Throttle
DOMAIN = "apcupsd"
REQUIREMENTS = ("apcaccess==0.0.4",)
CONF_HOST = "host"
CONF_PORT = "port"
CONF_TYPE = "type"
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 3551
KEY_STATUS = "STATUS"
VALUE_ONLINE = "ONLINE"
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
DATA = None
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Use config values to set up a function enabling status retrieval. """
global DATA
host = config[DOMAIN].get(CONF_HOST, DEFAULT_HOST)
port = config[DOMAIN].get(CONF_PORT, DEFAULT_PORT)
DATA = APCUPSdData(host, port)
# It doesn't really matter why we're not able to get the status, just that
# we can't.
# pylint: disable=broad-except
try:
DATA.update(no_throttle=True)
except Exception:
_LOGGER.exception("Failure while testing APCUPSd status retrieval.")
return False
return True
class APCUPSdData(object):
"""
Stores the data retrieved from APCUPSd for each entity to use, acts as the
single point responsible for fetching updates from the server.
"""
def __init__(self, host, port):
from apcaccess import status
self._host = host
self._port = port
self._status = None
self._get = status.get
self._parse = status.parse
@property
def status(self):
""" Get latest update if throttle allows. Return status. """
self.update()
return self._status
def _get_status(self):
""" Get the status from APCUPSd and parse it into a dict. """
return self._parse(self._get(host=self._host, port=self._port))
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self, **kwargs):
"""
Fetch the latest status from APCUPSd and store it in self._status.
"""
self._status = self._get_status()
@@ -0,0 +1,44 @@
"""
homeassistant.components.binary_sensor.apcupsd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides a binary sensor to track online status of a UPS.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.apcupsd/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components import apcupsd
DEPENDENCIES = [apcupsd.DOMAIN]
DEFAULT_NAME = "UPS Online Status"
def setup_platform(hass, config, add_entities, discovery_info=None):
""" Instantiate an OnlineStatus binary sensor entity and add it to HA. """
add_entities((OnlineStatus(config, apcupsd.DATA),))
class OnlineStatus(BinarySensorDevice):
""" Binary sensor to represent UPS online status. """
def __init__(self, config, data):
self._config = config
self._data = data
self._state = None
self.update()
@property
def name(self):
""" The name of the UPS online status sensor. """
return self._config.get("name", DEFAULT_NAME)
@property
def is_on(self):
""" True if the UPS is online, else False. """
return self._state == apcupsd.VALUE_ONLINE
def update(self):
"""
Get the status report from APCUPSd (or cache) and set this entity's
state.
"""
self._state = self._data.status[apcupsd.KEY_STATUS]
@@ -1,8 +1,11 @@
"""
homeassistant.components.binary_sensor.command_sensor
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure custom shell commands to turn a value
into a logical value for a binary sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.command/
"""
import logging
from datetime import timedelta
@@ -13,10 +13,11 @@ import homeassistant.components.nest as nest
from homeassistant.components.sensor.nest import NestSensor
from homeassistant.components.binary_sensor import BinarySensorDevice
DEPENDENCIES = ['nest']
BINARY_TYPES = ['fan',
'hvac_ac_state',
'hvac_aux_heater_state',
'hvac_heater_state',
'hvac_heat_x2_state',
'hvac_heat_x3_state',
'hvac_alt_heat_state',
@@ -21,7 +21,7 @@ DEFAULT_METHOD = 'GET'
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup REST binary sensors."""
""" Setup REST binary sensors. """
resource = config.get('resource', None)
method = config.get('method', DEFAULT_METHOD)
payload = config.get('payload', None)
@@ -41,10 +41,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments
class RestBinarySensor(BinarySensorDevice):
"""REST binary sensor."""
""" A REST binary sensor. """
def __init__(self, hass, rest, name, value_template):
"""Initialize a REST binary sensor."""
""" Initialize a REST binary sensor. """
self._hass = hass
self.rest = rest
self._name = name
@@ -54,12 +54,12 @@ class RestBinarySensor(BinarySensorDevice):
@property
def name(self):
"""Name of the binary sensor."""
""" Name of the binary sensor. """
return self._name
@property
def is_on(self):
"""Return if the binary sensor is on."""
""" Return if the binary sensor is on. """
if self.rest.data is None:
return False
@@ -69,5 +69,5 @@ class RestBinarySensor(BinarySensorDevice):
return bool(int(self.rest.data))
def update(self):
"""Get the latest data from REST API and updates the state."""
""" Get the latest data from REST API and updates the state. """
self.rest.update()
@@ -1,9 +1,11 @@
"""
homeassistant.components.binary_sensor.zigbee
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Contains functionality to use a ZigBee device as a binary sensor.
"""
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.zigbee/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.zigbee import (
ZigBeeDigitalIn, ZigBeeDigitalInConfig)
@@ -13,9 +15,7 @@ DEPENDENCIES = ["zigbee"]
def setup_platform(hass, config, add_entities, discovery_info=None):
"""
Create and add an entity based on the configuration.
"""
""" Create and add an entity based on the configuration. """
add_entities([
ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))
])
+77
View File
@@ -0,0 +1,77 @@
"""
homeassistant.components.bloomsky
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for BloomSky weather station.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/bloomsky/
"""
import logging
from datetime import timedelta
import requests
from homeassistant.util import Throttle
from homeassistant.helpers import validate_config
from homeassistant.const import CONF_API_KEY
DOMAIN = "bloomsky"
BLOOMSKY = None
_LOGGER = logging.getLogger(__name__)
# The BloomSky only updates every 5-8 minutes as per the API spec so there's
# no point in polling the API more frequently
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
# pylint: disable=unused-argument,too-few-public-methods
def setup(hass, config):
""" Setup BloomSky component. """
if not validate_config(
config,
{DOMAIN: [CONF_API_KEY]},
_LOGGER):
return False
api_key = config[DOMAIN][CONF_API_KEY]
global BLOOMSKY
try:
BLOOMSKY = BloomSky(api_key)
except RuntimeError:
return False
return True
class BloomSky(object):
""" Handle all communication with the BloomSky API. """
# API documentation at http://weatherlution.com/bloomsky-api/
API_URL = "https://api.bloomsky.com/api/skydata"
def __init__(self, api_key):
self._api_key = api_key
self.devices = {}
_LOGGER.debug("Initial bloomsky device load...")
self.refresh_devices()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def refresh_devices(self):
"""
Uses the API to retreive a list of devices associated with an
account along with all the sensors on the device.
"""
_LOGGER.debug("Fetching bloomsky update")
response = requests.get(self.API_URL,
headers={"Authorization": self._api_key},
timeout=10)
if response.status_code == 401:
raise RuntimeError("Invalid API_KEY")
elif response.status_code != 200:
_LOGGER.error("Invalid HTTP response: %s", response.status_code)
return
# create dictionary keyed off of the device unique id
self.devices.update({
device["DeviceID"]: device for device in response.json()
})
+30 -29
View File
@@ -33,8 +33,6 @@ SWITCH_ACTION_SNAPSHOT = 'snapshot'
SERVICE_CAMERA = 'camera_service'
STATE_RECORDING = 'recording'
DEFAULT_RECORDING_SECONDS = 30
# Maps discovered services to their platforms
@@ -46,6 +44,7 @@ DIR_DATETIME_FORMAT = '%Y-%m-%d_%H-%M-%S'
REC_DIR_PREFIX = 'recording-'
REC_IMG_PREFIX = 'recording_image-'
STATE_RECORDING = 'recording'
STATE_STREAMING = 'streaming'
STATE_IDLE = 'idle'
@@ -121,33 +120,7 @@ def setup(hass, config):
try:
camera.is_streaming = True
camera.update_ha_state()
handler.request.sendall(bytes('HTTP/1.1 200 OK\r\n', 'utf-8'))
handler.request.sendall(bytes(
'Content-type: multipart/x-mixed-replace; \
boundary=--jpgboundary\r\n\r\n', 'utf-8'))
handler.request.sendall(bytes('--jpgboundary\r\n', 'utf-8'))
# MJPEG_START_HEADER.format()
while True:
img_bytes = camera.camera_image()
if img_bytes is None:
continue
headers_str = '\r\n'.join((
'Content-length: {}'.format(len(img_bytes)),
'Content-type: image/jpeg',
)) + '\r\n\r\n'
handler.request.sendall(
bytes(headers_str, 'utf-8') +
img_bytes +
bytes('\r\n', 'utf-8'))
handler.request.sendall(
bytes('--jpgboundary\r\n', 'utf-8'))
time.sleep(0.5)
camera.mjpeg_stream(handler)
except (requests.RequestException, IOError):
camera.is_streaming = False
@@ -190,6 +163,34 @@ class Camera(Entity):
""" Return bytes of camera image. """
raise NotImplementedError()
def mjpeg_stream(self, handler):
""" Generate an HTTP MJPEG stream from camera images. """
handler.request.sendall(bytes('HTTP/1.1 200 OK\r\n', 'utf-8'))
handler.request.sendall(bytes(
'Content-type: multipart/x-mixed-replace; \
boundary=--jpgboundary\r\n\r\n', 'utf-8'))
handler.request.sendall(bytes('--jpgboundary\r\n', 'utf-8'))
# MJPEG_START_HEADER.format()
while True:
img_bytes = self.camera_image()
if img_bytes is None:
continue
headers_str = '\r\n'.join((
'Content-length: {}'.format(len(img_bytes)),
'Content-type: image/jpeg',
)) + '\r\n\r\n'
handler.request.sendall(
bytes(headers_str, 'utf-8') +
img_bytes +
bytes('\r\n', 'utf-8'))
handler.request.sendall(
bytes('--jpgboundary\r\n', 'utf-8'))
time.sleep(0.5)
@property
def state(self):
""" Returns the state of the entity. """
@@ -0,0 +1,60 @@
"""
homeassistant.components.camera.bloomsky
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for a camera of a BloomSky weather station.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/camera.bloomsky/
"""
import logging
import requests
import homeassistant.components.bloomsky as bloomsky
from homeassistant.components.camera import Camera
DEPENDENCIES = ["bloomsky"]
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" set up access to BloomSky cameras """
for device in bloomsky.BLOOMSKY.devices.values():
add_devices_callback([BloomSkyCamera(bloomsky.BLOOMSKY, device)])
class BloomSkyCamera(Camera):
""" Represents the images published from the BloomSky's camera. """
def __init__(self, bs, device):
""" set up for access to the BloomSky camera images """
super(BloomSkyCamera, self).__init__()
self._name = device["DeviceName"]
self._id = device["DeviceID"]
self._bloomsky = bs
self._url = ""
self._last_url = ""
# _last_image will store images as they are downloaded so that the
# frequent updates in home-assistant don't keep poking the server
# to download the same image over and over
self._last_image = ""
self._logger = logging.getLogger(__name__)
def camera_image(self):
""" Update the camera's image if it has changed. """
try:
self._url = self._bloomsky.devices[self._id]["Data"]["ImageURL"]
self._bloomsky.refresh_devices()
# if the url hasn't changed then the image hasn't changed
if self._url != self._last_url:
response = requests.get(self._url, timeout=10)
self._last_url = self._url
self._last_image = response.content
except requests.exceptions.RequestException as error:
self._logger.error("Error getting bloomsky image: %s", error)
return None
return self._last_image
@property
def name(self):
""" The name of this BloomSky device. """
return self._name
+30 -10
View File
@@ -14,6 +14,9 @@ from requests.auth import HTTPBasicAuth
from homeassistant.helpers import validate_config
from homeassistant.components.camera import DOMAIN, Camera
from homeassistant.const import HTTP_OK
CONTENT_TYPE_HEADER = 'Content-Type'
_LOGGER = logging.getLogger(__name__)
@@ -41,6 +44,17 @@ class MjpegCamera(Camera):
self._password = device_info.get('password')
self._mjpeg_url = device_info['mjpeg_url']
def camera_stream(self):
""" Return a mjpeg stream image response directly from the camera. """
if self._username and self._password:
return requests.get(self._mjpeg_url,
auth=HTTPBasicAuth(self._username,
self._password),
stream=True)
else:
return requests.get(self._mjpeg_url,
stream=True)
def camera_image(self):
""" Return a still image response from the camera. """
@@ -55,16 +69,22 @@ class MjpegCamera(Camera):
jpg = data[jpg_start:jpg_end + 2]
return jpg
if self._username and self._password:
with closing(requests.get(self._mjpeg_url,
auth=HTTPBasicAuth(self._username,
self._password),
stream=True)) as response:
return process_response(response)
else:
with closing(requests.get(self._mjpeg_url,
stream=True)) as response:
return process_response(response)
with closing(self.camera_stream()) as response:
return process_response(response)
def mjpeg_stream(self, handler):
""" Generate an HTTP MJPEG stream from the camera. """
response = self.camera_stream()
content_type = response.headers[CONTENT_TYPE_HEADER]
handler.send_response(HTTP_OK)
handler.send_header(CONTENT_TYPE_HEADER, content_type)
handler.end_headers()
for chunk in response.iter_content(chunk_size=1024):
if not chunk:
break
handler.wfile.write(chunk)
@property
def name(self):
+91
View File
@@ -0,0 +1,91 @@
"""
homeassistant.components.camera.uvc
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Ubiquiti's UVC cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.uvc/
"""
import logging
import socket
import requests
from homeassistant.helpers import validate_config
from homeassistant.components.camera import DOMAIN, Camera
REQUIREMENTS = ['uvcclient==0.5']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Discover cameras on a Unifi NVR. """
if not validate_config({DOMAIN: config}, {DOMAIN: ['nvr', 'key']},
_LOGGER):
return None
addr = config.get('nvr')
port = int(config.get('port', 7080))
key = config.get('key')
from uvcclient import nvr
nvrconn = nvr.UVCRemote(addr, port, key)
try:
cameras = nvrconn.index()
except nvr.NotAuthorized:
_LOGGER.error('Authorization failure while connecting to NVR')
return False
except nvr.NvrError:
_LOGGER.error('NVR refuses to talk to me')
return False
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to NVR: %s', str(ex))
return False
for camera in cameras:
add_devices([UnifiVideoCamera(nvrconn,
camera['uuid'],
camera['name'])])
class UnifiVideoCamera(Camera):
""" A Ubiquiti Unifi Video Camera. """
def __init__(self, nvr, uuid, name):
super(UnifiVideoCamera, self).__init__()
self._nvr = nvr
self._uuid = uuid
self._name = name
self.is_streaming = False
@property
def name(self):
return self._name
@property
def is_recording(self):
caminfo = self._nvr.get_camera(self._uuid)
return caminfo['recordingSettings']['fullTimeRecordEnabled']
def camera_image(self):
from uvcclient import camera as uvc_camera
caminfo = self._nvr.get_camera(self._uuid)
camera = None
for addr in [caminfo['host'], caminfo['internalHost']]:
try:
camera = uvc_camera.UVCCameraClient(addr,
caminfo['username'],
'ubnt')
_LOGGER.debug('Logged into UVC camera %(name)s via %(addr)s',
dict(name=self._name, addr=addr))
except socket.error:
pass
if not camera:
_LOGGER.error('Unable to login to camera')
return None
camera.login()
return camera.get_snapshot()
+1 -1
View File
@@ -141,7 +141,7 @@ class Configurator(object):
state = self.hass.states.get(entity_id)
new_data = state.attributes
new_data = dict(state.attributes)
new_data[ATTR_ERRORS] = error
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
+1
View File
@@ -21,6 +21,7 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
'binary_sensor',
'camera',
'device_tracker',
'garage_door',
'light',
'lock',
'media_player',
@@ -11,7 +11,6 @@ import logging
from datetime import timedelta
import re
import threading
import telnetlib
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers import validate_config
@@ -21,6 +20,7 @@ from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
REQUIREMENTS = ['pexpect==4.0.1']
_LOGGER = logging.getLogger(__name__)
_DEVICES_REGEX = re.compile(
@@ -44,6 +44,7 @@ def get_scanner(hass, config):
class ArubaDeviceScanner(object):
""" This class queries a Aruba Acces Point for connected devices. """
def __init__(self, config):
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
@@ -93,23 +94,39 @@ class ArubaDeviceScanner(object):
def get_aruba_data(self):
""" Retrieve data from Aruba Access Point and return parsed result. """
try:
telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'User: ')
telnet.write((self.username + '\r\n').encode('ascii'))
telnet.read_until(b'Password: ')
telnet.write((self.password + '\r\n').encode('ascii'))
telnet.read_until(b'#')
telnet.write(('show clients\r\n').encode('ascii'))
devices_result = telnet.read_until(b'#').split(b'\r\n')
telnet.write('exit\r\n'.encode('ascii'))
except EOFError:
_LOGGER.exception("Unexpected response from router")
import pexpect
connect = "ssh {}@{}"
ssh = pexpect.spawn(connect.format(self.username, self.host))
query = ssh.expect(['password:', pexpect.TIMEOUT, pexpect.EOF,
'continue connecting (yes/no)?',
'Host key verification failed.',
'Connection refused',
'Connection timed out'], timeout=120)
if query == 1:
_LOGGER.error("Timeout")
return
except ConnectionRefusedError:
_LOGGER.exception("Connection refused by router," +
" is telnet enabled?")
elif query == 2:
_LOGGER.error("Unexpected response from router")
return
elif query == 3:
ssh.sendline('yes')
ssh.expect('password:')
elif query == 4:
_LOGGER.error("Host key Changed")
return
elif query == 5:
_LOGGER.error("Connection refused by server")
return
elif query == 6:
_LOGGER.error("Connection timed out")
return
ssh.sendline(self.password)
ssh.expect('#')
ssh.sendline('show clients')
ssh.expect('#')
devices_result = ssh.before.split(b'\r\n')
ssh.sendline('exit')
devices = {}
for device in devices_result:
@@ -119,5 +136,5 @@ class ArubaDeviceScanner(object):
'ip': match.group('ip'),
'mac': match.group('mac').upper(),
'name': match.group('name')
}
}
return devices
@@ -95,7 +95,8 @@ def setup_scanner(hass, config, see):
MOBILE_BEACONS_ACTIVE[dev_id].append(location)
else:
# Normal region
kwargs['location_name'] = location
if not zone.attributes.get('passive'):
kwargs['location_name'] = location
regions = REGIONS_ENTERED[dev_id]
if location not in regions:
@@ -115,7 +116,8 @@ def setup_scanner(hass, config, see):
if new_region:
# Exit to previous region
zone = hass.states.get("zone.{}".format(new_region))
kwargs['location_name'] = new_region
if not zone.attributes.get('passive'):
kwargs['location_name'] = new_region
_set_gps_from_zone(kwargs, zone)
_LOGGER.info("Exit from to %s", new_region)
@@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by update_mdi script """
VERSION = "a2605736c8d959d50c4bcbba1e6a6aa5"
VERSION = "a1a203680639ff1abcc7b68cdb29c57a"
+1 -1
View File
@@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "1e89871aaae43c91b2508f52bc161b69"
VERSION = "833d09737fec24f9219efae87c5bfd2a"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
!function(e){function t(r){if(n[r])return n[r].exports;var s=n[r]={exports:{},id:r,loaded:!1};return e[r].call(s.exports,s,s.exports,t),s.loaded=!0,s.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([/*!*************************************!*\
!*** ./src/service-worker/index.js ***!
\*************************************/
function(e,t,n){"use strict";var r="0.10",s="/",c=["/","/logbook","/history","/map","/devService","/devState","/devEvent","/devInfo","/states"],i=["/static/favicon-192x192.png"];self.addEventListener("install",function(e){e.waitUntil(caches.open(r).then(function(e){return e.addAll(i.concat(s))}))}),self.addEventListener("activate",function(e){}),self.addEventListener("message",function(e){}),self.addEventListener("fetch",function(e){var t=e.request.url.substr(e.request.url.indexOf("/",8));i.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(e.request)})),c.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(s).then(function(n){var r=fetch(e.request).then(function(e){return t.put(s,e.clone()),e});return n||r})}))})}]);
function(e,t,n){"use strict";var r="0.10",s="/",c=["/","/logbook","/history","/map","/devService","/devState","/devEvent","/devInfo","/states"],i=["/static/favicon-192x192.png"];self.addEventListener("install",function(e){e.waitUntil(caches.open(r).then(function(e){return e.addAll(i.concat(s))}))}),self.addEventListener("activate",function(e){}),self.addEventListener("message",function(e){}),self.addEventListener("fetch",function(e){var t=e.request.url.substr(e.request.url.indexOf("/",8));i.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(e.request)})),c.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(s).then(function(n){return n||fetch(e.request).then(function(e){return t.put(s,e.clone()),e})})}))})}]);
//# sourceMappingURL=service_worker.js.map
@@ -0,0 +1,108 @@
"""
homeassistant.components.garage_door
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to interface with garage doors that can be controlled remotely.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/garage_door/
"""
import logging
import os
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN, SERVICE_CLOSE, SERVICE_OPEN,
ATTR_ENTITY_ID)
from homeassistant.components import (group, wink)
DOMAIN = 'garage_door'
SCAN_INTERVAL = 30
GROUP_NAME_ALL_GARAGE_DOORS = 'all garage doors'
ENTITY_ID_ALL_GARAGE_DOORS = group.ENTITY_ID_FORMAT.format('all_garage_doors')
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
wink.DISCOVER_GARAGE_DOORS: 'wink'
}
_LOGGER = logging.getLogger(__name__)
def is_closed(hass, entity_id=None):
""" Returns if the garage door is closed based on the statemachine. """
entity_id = entity_id or ENTITY_ID_ALL_GARAGE_DOORS
return hass.states.is_state(entity_id, STATE_CLOSED)
def close_door(hass, entity_id=None):
""" Closes all or specified garage door. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_CLOSE, data)
def open_door(hass, entity_id=None):
""" Open all or specified garage door. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_OPEN, data)
def setup(hass, config):
""" Track states and offer events for garage door. """
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
GROUP_NAME_ALL_GARAGE_DOORS)
component.setup(config)
def handle_garage_door_service(service):
""" Handles calls to the garage door services. """
target_locks = component.extract_from_service(service)
for item in target_locks:
if service.service == SERVICE_CLOSE:
item.close_door()
else:
item.open_door()
if item.should_poll:
item.update_ha_state(True)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_OPEN, handle_garage_door_service,
descriptions.get(SERVICE_OPEN))
hass.services.register(DOMAIN, SERVICE_CLOSE, handle_garage_door_service,
descriptions.get(SERVICE_CLOSE))
return True
class GarageDoorDevice(Entity):
""" Represents a garage door. """
# pylint: disable=no-self-use
@property
def is_closed(self):
""" Is the garage door closed or opened. """
return None
def close_door(self):
""" Closes the garage door. """
raise NotImplementedError()
def open_door(self):
""" Opens the garage door. """
raise NotImplementedError()
@property
def state(self):
""" State of the garage door. """
closed = self.is_closed
if closed is None:
return STATE_UNKNOWN
return STATE_CLOSED if closed else STATE_OPEN
@@ -0,0 +1,48 @@
"""
homeassistant.components.garage_door.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo platform that has two fake garage doors.
"""
from homeassistant.components.garage_door import GarageDoorDevice
from homeassistant.const import STATE_CLOSED, STATE_OPEN
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return demo garage doors. """
add_devices_callback([
DemoGarageDoor('Left Garage Door', STATE_CLOSED),
DemoGarageDoor('Right Garage Door', STATE_OPEN)
])
class DemoGarageDoor(GarageDoorDevice):
""" Provides a demo garage door. """
def __init__(self, name, state):
self._name = name
self._state = state
@property
def should_poll(self):
""" No polling needed for a demo garage door. """
return False
@property
def name(self):
""" Returns the name of the device if any. """
return self._name
@property
def is_closed(self):
""" True if device is closed. """
return self._state == STATE_CLOSED
def close_door(self, **kwargs):
""" Close the device. """
self._state = STATE_CLOSED
self.update_ha_state()
def open_door(self, **kwargs):
""" Open the device. """
self._state = STATE_OPEN
self.update_ha_state()
@@ -0,0 +1,67 @@
"""
homeassistant.components.garage_door.wink
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Wink garage doors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/garage_door.wink/
"""
import logging
from homeassistant.components.garage_door import GarageDoorDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.5.0']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Wink platform. """
import pywink
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token. "
"Get one at https://winkbearertoken.appspot.com/")
return
pywink.set_bearer_token(token)
add_devices(WinkGarageDoorDevice(door) for door in
pywink.get_garage_doors())
class WinkGarageDoorDevice(GarageDoorDevice):
""" Represents a Wink garage door. """
def __init__(self, wink):
self.wink = wink
@property
def unique_id(self):
""" Returns the id of this wink garage door """
return "{}.{}".format(self.__class__, self.wink.device_id())
@property
def name(self):
""" Returns the name of the garage door if any. """
return self.wink.name()
def update(self):
""" Update the state of the garage door. """
self.wink.update_state()
@property
def is_closed(self):
""" True if device is closed. """
return self.wink.state() == 0
def close_door(self):
""" Close the device. """
self.wink.set_state(0)
def open_door(self):
""" Open the device. """
self.wink.set_state(1)
+122
View File
@@ -0,0 +1,122 @@
"""
homeassistant.components.graphite
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component that records all events and state changes and feeds the data to
a graphite installation.
Example configuration:
graphite:
host: foobar
port: 2003
prefix: ha
All config elements are optional, and assumed to be on localhost at the
default port if not specified. Prefix is the metric prefix in graphite,
and defaults to 'ha'.
"""
import logging
import queue
import socket
import threading
import time
from homeassistant.const import (
EVENT_STATE_CHANGED,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import state
DOMAIN = "graphite"
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Setup graphite feeder. """
graphite_config = config.get('graphite', {})
host = graphite_config.get('host', 'localhost')
prefix = graphite_config.get('prefix', 'ha')
try:
port = int(graphite_config.get('port', 2003))
except ValueError:
_LOGGER.error('Invalid port specified')
return False
GraphiteFeeder(hass, host, port, prefix)
return True
class GraphiteFeeder(threading.Thread):
""" Feeds data to graphite. """
def __init__(self, hass, host, port, prefix):
super(GraphiteFeeder, self).__init__(daemon=True)
self._hass = hass
self._host = host
self._port = port
# rstrip any trailing dots in case they think they
# need it
self._prefix = prefix.rstrip('.')
self._queue = queue.Queue()
self._quit_object = object()
hass.bus.listen_once(EVENT_HOMEASSISTANT_START,
self.start_listen)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
self.shutdown)
hass.bus.listen(EVENT_STATE_CHANGED, self.event_listener)
def start_listen(self, event):
""" Start event-processing thread. """
self.start()
def shutdown(self, event):
""" Tell the thread that we are done.
This does not block because there is nothing to
clean up (and no penalty for killing in-process
connections to graphite.
"""
self._queue.put(self._quit_object)
def event_listener(self, event):
""" Queue an event for processing. """
self._queue.put(event)
def _send_to_graphite(self, data):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(10)
sock.connect((self._host, self._port))
sock.sendall(data.encode('ascii'))
sock.send('\n'.encode('ascii'))
sock.close()
def _report_attributes(self, entity_id, new_state):
now = time.time()
things = dict(new_state.attributes)
try:
things['state'] = state.state_as_number(new_state)
except ValueError:
pass
lines = ['%s.%s.%s %f %i' % (self._prefix,
entity_id, key.replace(' ', '_'),
value, now)
for key, value in things.items()
if isinstance(value, (float, int))]
if not lines:
return
_LOGGER.debug('Sending to graphite: %s', lines)
try:
self._send_to_graphite('\n'.join(lines))
except socket.error:
_LOGGER.exception('Failed to send data to graphite')
def run(self):
while True:
event = self._queue.get()
if event == self._quit_object:
self._queue.task_done()
return
elif (event.event_type == EVENT_STATE_CHANGED and
'new_state' in event.data):
self._report_attributes(event.data['entity_id'],
event.data['new_state'])
self._queue.task_done()
+1 -1
View File
@@ -71,7 +71,7 @@ def expand_entity_ids(hass, entity_ids):
if domain == DOMAIN:
found_ids.extend(
ent_id for ent_id
in get_entity_ids(hass, entity_id)
in expand_entity_ids(hass, get_entity_ids(hass, entity_id))
if ent_id not in found_ids)
else:
+62 -15
View File
@@ -18,6 +18,8 @@ from homeassistant.const import HTTP_BAD_REQUEST
DOMAIN = 'history'
DEPENDENCIES = ['recorder', 'http']
SIGNIFICANT_DOMAINS = ('thermostat',)
URL_HISTORY_PERIOD = re.compile(
r'/api/history/period(?:/(?P<date>\d{4}-\d{1,2}-\d{1,2})|)')
@@ -35,6 +37,37 @@ def last_5_states(entity_id):
return recorder.query_states(query, (entity_id, ))
def get_significant_states(start_time, end_time=None, entity_id=None):
"""Return states changes during UTC period start_time - end_time.
Significant states are all states where there is a state change,
as well as all states from certain domains (for instance
thermostat so that we get current temperature in our graphs).
"""
where = """
(domain in ({}) or last_changed=last_updated)
AND last_updated > ?
""".format(",".join(["'%s'" % x for x in SIGNIFICANT_DOMAINS]))
data = [start_time]
if end_time is not None:
where += "AND last_updated < ? "
data.append(end_time)
if entity_id is not None:
where += "AND entity_id = ? "
data.append(entity_id.lower())
query = ("SELECT * FROM states WHERE {} "
"ORDER BY entity_id, last_updated ASC").format(where)
states = recorder.query_states(query, data)
return states_to_json(states, start_time, entity_id)
def state_changes_during_period(start_time, end_time=None, entity_id=None):
"""
Return states changes during UTC period start_time - end_time.
@@ -55,20 +88,7 @@ def state_changes_during_period(start_time, end_time=None, entity_id=None):
states = recorder.query_states(query, data)
result = defaultdict(list)
entity_ids = [entity_id] if entity_id is not None else None
# Get the states at the start time
for state in get_states(start_time, entity_ids):
state.last_changed = start_time
result[state.entity_id].append(state)
# Append all changes to it
for entity_id, group in groupby(states, lambda state: state.entity_id):
result[entity_id].extend(group)
return result
return states_to_json(states, start_time, entity_id)
def get_states(utc_point_in_time, entity_ids=None, run=None):
@@ -100,6 +120,33 @@ def get_states(utc_point_in_time, entity_ids=None, run=None):
return recorder.query_states(query, where_data)
def states_to_json(states, start_time, entity_id):
"""Converts SQL results into JSON friendly data structure.
This takes our state list and turns it into a JSON friendly data
structure {'entity_id': [list of states], 'entity_id2': [list of states]}
We also need to go back and create a synthetic zero data point for
each list of states, otherwise our graphs won't start on the Y
axis correctly.
"""
result = defaultdict(list)
entity_ids = [entity_id] if entity_id is not None else None
# Get the states at the start time
for state in get_states(start_time, entity_ids):
state.last_changed = start_time
state.last_updated = start_time
result[state.entity_id].append(state)
# Append all changes to it
for entity_id, group in groupby(states, lambda state: state.entity_id):
result[entity_id].extend(group)
return result
def get_state(utc_point_in_time, entity_id, run=None):
""" Return a state at a specific point in time. """
states = get_states(utc_point_in_time, (entity_id,), run)
@@ -152,4 +199,4 @@ def _api_history_period(handler, path_match, data):
entity_id = data.get('filter_entity_id')
handler.write_json(
state_changes_during_period(start_time, end_time, entity_id).values())
get_significant_states(start_time, end_time, entity_id).values())
+22 -22
View File
@@ -9,10 +9,8 @@ https://home-assistant.io/components/influxdb/
import logging
import homeassistant.util as util
from homeassistant.helpers import validate_config
from homeassistant.const import (EVENT_STATE_CHANGED, STATE_ON, STATE_OFF,
STATE_UNLOCKED, STATE_LOCKED, STATE_UNKNOWN)
from homeassistant.components.sun import (STATE_ABOVE_HORIZON,
STATE_BELOW_HORIZON)
from homeassistant.helpers import state as state_helper
from homeassistant.const import (EVENT_STATE_CHANGED, STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__)
@@ -22,14 +20,18 @@ DEPENDENCIES = []
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 8086
DEFAULT_DATABASE = 'home_assistant'
DEFAULT_SSL = False
DEFAULT_VERIFY_SSL = False
REQUIREMENTS = ['influxdb==2.11.0']
REQUIREMENTS = ['influxdb==2.12.0']
CONF_HOST = 'host'
CONF_PORT = 'port'
CONF_DB_NAME = 'database'
CONF_USERNAME = 'username'
CONF_PASSWORD = 'password'
CONF_SSL = 'ssl'
CONF_VERIFY_SSL = 'verify_ssl'
def setup(hass, config):
@@ -37,7 +39,9 @@ def setup(hass, config):
from influxdb import InfluxDBClient, exceptions
if not validate_config(config, {DOMAIN: ['host']}, _LOGGER):
if not validate_config(config, {DOMAIN: ['host',
CONF_USERNAME,
CONF_PASSWORD]}, _LOGGER):
return False
conf = config[DOMAIN]
@@ -47,10 +51,14 @@ def setup(hass, config):
database = util.convert(conf.get(CONF_DB_NAME), str, DEFAULT_DATABASE)
username = util.convert(conf.get(CONF_USERNAME), str)
password = util.convert(conf.get(CONF_PASSWORD), str)
ssl = util.convert(conf.get(CONF_SSL), bool, DEFAULT_SSL)
verify_ssl = util.convert(conf.get(CONF_VERIFY_SSL), bool,
DEFAULT_VERIFY_SSL)
try:
influx = InfluxDBClient(host=host, port=port, username=username,
password=password, database=database)
password=password, database=database,
ssl=ssl, verify_ssl=verify_ssl)
influx.query("select * from /.*/ LIMIT 1;")
except exceptions.InfluxDBClientError as exc:
_LOGGER.error("Database host is not accessible due to '%s', please "
@@ -62,25 +70,17 @@ def setup(hass, config):
""" Listen for new messages on the bus and sends them to Influx. """
state = event.data.get('new_state')
if state is None:
if state is None or state.state in (STATE_UNKNOWN, ''):
return
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON):
_state = 1
elif state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
STATE_BELOW_HORIZON):
_state = 0
else:
try:
_state = state_helper.state_as_number(state)
except ValueError:
_state = state.state
if _state == '':
return
try:
_state = float(_state)
except ValueError:
pass
measurement = state.attributes.get('unit_of_measurement', state.domain)
measurement = state.attributes.get('unit_of_measurement')
if measurement in (None, ''):
measurement = state.entity_id
json_body = [
{
+4 -4
View File
@@ -41,7 +41,7 @@ def turn_off(hass, entity_id):
def setup(hass, config):
"""Set up input booleans."""
""" Set up input boolean. """
if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False
@@ -68,7 +68,7 @@ def setup(hass, config):
return False
def toggle_service(service):
"""Handle a calls to the input boolean services."""
""" Handle a calls to the input boolean services. """
target_inputs = component.extract_from_service(service)
for input_b in target_inputs:
@@ -86,10 +86,10 @@ def setup(hass, config):
class InputBoolean(ToggleEntity):
"""Represent a boolean input within Home Assistant."""
""" Represent a boolean input. """
def __init__(self, object_id, name, state, icon):
"""Initialize a boolean input."""
""" Initialize a boolean input. """
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
self._state = state
+140
View File
@@ -0,0 +1,140 @@
"""
homeassistant.components.input_select
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to offer a way to select an option from a list.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/input_select/
"""
import logging
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
DOMAIN = 'input_select'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__)
CONF_NAME = 'name'
CONF_INITIAL = 'initial'
CONF_ICON = 'icon'
CONF_OPTIONS = 'options'
ATTR_OPTION = 'option'
ATTR_OPTIONS = 'options'
SERVICE_SELECT_OPTION = 'select_option'
def select_option(hass, entity_id, option):
""" Set input_select to False. """
hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, {
ATTR_ENTITY_ID: entity_id,
ATTR_OPTION: option,
})
def setup(hass, config):
""" Set up input select. """
if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False
component = EntityComponent(_LOGGER, DOMAIN, hass)
entities = []
for object_id, cfg in config[DOMAIN].items():
if object_id != slugify(object_id):
_LOGGER.warning("Found invalid key for boolean input: %s. "
"Use %s instead", object_id, slugify(object_id))
continue
if not cfg:
_LOGGER.warning("No configuration specified for %s", object_id)
continue
name = cfg.get(CONF_NAME)
options = cfg.get(CONF_OPTIONS)
if not isinstance(options, list) or len(options) == 0:
_LOGGER.warning('Key %s should be a list of options', CONF_OPTIONS)
continue
options = [str(val) for val in options]
state = cfg.get(CONF_INITIAL)
if state not in options:
state = options[0]
icon = cfg.get(CONF_ICON)
entities.append(InputSelect(object_id, name, state, options, icon))
if not entities:
return False
def select_option_service(call):
""" Handle a calls to the input select services. """
target_inputs = component.extract_from_service(call)
for input_select in target_inputs:
input_select.select_option(call.data.get(ATTR_OPTION))
hass.services.register(DOMAIN, SERVICE_SELECT_OPTION,
select_option_service)
component.add_entities(entities)
return True
class InputSelect(Entity):
""" Represent a select input. """
# pylint: disable=too-many-arguments
def __init__(self, object_id, name, state, options, icon):
""" Initialize a select input. """
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
self._current_option = state
self._options = options
self._icon = icon
@property
def should_poll(self):
""" If entity should be polled. """
return False
@property
def name(self):
""" Name of the select input. """
return self._name
@property
def icon(self):
""" Icon to be used for this entity. """
return self._icon
@property
def state(self):
""" State of the component. """
return self._current_option
@property
def state_attributes(self):
""" State attributes. """
return {
ATTR_OPTIONS: self._options,
}
def select_option(self, option):
""" Select new option. """
if option not in self._options:
_LOGGER.warning('Invalid option: %s (possible options: %s)',
option, ', '.join(self._options))
return
self._current_option = option
self.update_ha_state()
+3 -3
View File
@@ -1,10 +1,10 @@
"""
homeassistant.components.insteon
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
homeassistant.components.insteon_hub
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Insteon Hub.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/insteon/
https://home-assistant.io/components/insteon_hub/
"""
import logging
import homeassistant.bootstrap as bootstrap
+2 -11
View File
@@ -11,7 +11,7 @@ import os
import csv
from homeassistant.components import (
group, discovery, wink, isy994, zwave, insteon_hub)
group, discovery, wink, isy994, zwave, insteon_hub, mysensors)
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
@@ -64,6 +64,7 @@ DISCOVERY_PLATFORMS = {
isy994.DISCOVER_LIGHTS: 'isy994',
discovery.SERVICE_HUE: 'hue',
zwave.DISCOVER_LIGHTS: 'zwave',
mysensors.DISCOVER_LIGHTS: 'mysensors',
}
PROP_TO_ATTR = {
@@ -300,11 +301,6 @@ class Light(ToggleEntity):
""" CT color value in mirads. """
return None
@property
def device_state_attributes(self):
""" Returns device specific state attributes. """
return None
@property
def state_attributes(self):
""" Returns optional state attributes. """
@@ -322,9 +318,4 @@ class Light(ToggleEntity):
data[ATTR_XY_COLOR][0], data[ATTR_XY_COLOR][1],
data[ATTR_BRIGHTNESS])
device_attr = self.device_state_attributes
if device_attr is not None:
data.update(device_attr)
return data
+10 -2
View File
@@ -16,7 +16,7 @@ from homeassistant.components.light import \
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['liffylights==0.9.0']
REQUIREMENTS = ['liffylights==0.9.4']
DEPENDENCIES = []
CONF_SERVER = "server" # server address configuration item
@@ -58,10 +58,18 @@ class LIFX():
bulb = self.find_bulb(ipaddr)
if bulb is None:
_LOGGER.debug("new bulb %s %s %d %d %d %d %d",
ipaddr, name, power, hue, sat, bri, kel)
bulb = LIFXLight(self._liffylights, ipaddr, name,
power, hue, sat, bri, kel)
self._devices.append(bulb)
self._add_devices_callback([bulb])
else:
_LOGGER.debug("update bulb %s %s %d %d %d %d %d",
ipaddr, name, power, hue, sat, bri, kel)
bulb.set_power(power)
bulb.set_color(hue, sat, bri, kel)
bulb.update_ha_state()
# pylint: disable=too-many-arguments
def on_color(self, ipaddr, hue, sat, bri, kel):
@@ -95,7 +103,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
lifx_library = LIFX(add_devices_callback, server_addr, broadcast_addr)
# register our poll service
track_time_change(hass, lifx_library.poll, second=10)
track_time_change(hass, lifx_library.poll, second=[10, 40])
lifx_library.probe()
+226
View File
@@ -0,0 +1,226 @@
"""
homeassistant.components.light.mysensors.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for MySensors lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.mysensors.html
"""
import logging
from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR)
from homeassistant.const import (
ATTR_BATTERY_LEVEL,
STATE_ON, STATE_OFF)
import homeassistant.components.mysensors as mysensors
_LOGGER = logging.getLogger(__name__)
ATTR_RGB_WHITE = 'rgb_white'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors platform for sensors."""
# Only act if loaded via mysensors by discovery event.
# Otherwise gateway is not setup.
if discovery_info is None:
return
for gateway in mysensors.GATEWAYS.values():
# Define the S_TYPES and V_TYPES that the platform should handle as
# states. Map them in a dict of lists.
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_LIGHT: [set_req.V_LIGHT],
pres.S_DIMMER: [set_req.V_DIMMER],
}
if float(gateway.version) >= 1.5:
# Add V_RGBW when rgb_white is implemented in the frontend
map_sv_types.update({
pres.S_RGB_LIGHT: [set_req.V_RGB],
})
map_sv_types[pres.S_LIGHT].append(set_req.V_STATUS)
map_sv_types[pres.S_DIMMER].append(set_req.V_PERCENTAGE)
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsLight))
class MySensorsLight(Light):
"""Represent the value of a MySensors child node."""
# pylint: disable=too-many-arguments,too-many-instance-attributes
def __init__(self, gateway, node_id, child_id, name, value_type):
"""Setup instance attributes."""
self.gateway = gateway
self.node_id = node_id
self.child_id = child_id
self._name = name
self.value_type = value_type
self.battery_level = 0
self._values = {}
self._state = None
self._rgb = None
self._brightness = None
self._white = None
@property
def should_poll(self):
"""MySensor gateway pushes its state to HA."""
return False
@property
def name(self):
"""The name of this entity."""
return self._name
@property
def brightness(self):
"""Brightness of this light between 0..255."""
return self._brightness
@property
def rgb_color(self):
"""RGB color value [int, int, int]."""
return self._rgb
@property
def rgb_white(self): # not implemented in the frontend yet
"""White value in RGBW, value between 0..255."""
return self._white
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
device_attr = {
mysensors.ATTR_PORT: self.gateway.port,
mysensors.ATTR_NODE_ID: self.node_id,
mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level,
}
for value_type, value in self._values.items():
device_attr[self.gateway.const.SetReq(value_type).name] = value
return device_attr
@property
def is_on(self):
"""True if device is on."""
return self._state
def turn_on(self, **kwargs):
"""Turn the device on."""
set_req = self.gateway.const.SetReq
rgb = self._rgb
brightness = self._brightness
white = self._white
if set_req.V_LIGHT in self._values and not self._state:
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_LIGHT, 1)
if ATTR_BRIGHTNESS in kwargs and set_req.V_DIMMER in self._values and \
kwargs[ATTR_BRIGHTNESS] != self._brightness:
brightness = kwargs[ATTR_BRIGHTNESS]
percent = round(100 * brightness / 255)
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_DIMMER, percent)
if float(self.gateway.version) >= 1.5:
if ATTR_RGB_WHITE in kwargs and \
self.value_type in (set_req.V_RGB, set_req.V_RGBW) and \
kwargs[ATTR_RGB_WHITE] != self._white:
white = kwargs[ATTR_RGB_WHITE]
if ATTR_RGB_COLOR in kwargs and \
self.value_type in (set_req.V_RGB, set_req.V_RGBW) and \
kwargs[ATTR_RGB_COLOR] != self._rgb:
rgb = kwargs[ATTR_RGB_COLOR]
if set_req.V_RGBW == self.value_type:
hex_template = '%02x%02x%02x%02x'
color_list = rgb.append(white)
if set_req.V_RGB == self.value_type:
hex_template = '%02x%02x%02x'
color_list = rgb
hex_color = hex_template % tuple(color_list)
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type, hex_color)
if self.gateway.optimistic:
# optimistically assume that light has changed state
self._state = True
self._rgb = rgb
self._brightness = brightness
self._white = white
self.update_ha_state()
def turn_off(self, **kwargs):
"""Turn the device off."""
set_req = self.gateway.const.SetReq
v_type = set_req.V_LIGHT
value = 0
if set_req.V_LIGHT in self._values:
self._values[set_req.V_LIGHT] = STATE_OFF
elif set_req.V_DIMMER in self._values:
v_type = set_req.V_DIMMER
elif float(self.gateway.version) >= 1.5:
if set_req.V_RGB in self._values:
v_type = set_req.V_RGB
value = '000000'
elif set_req.V_RGBW in self._values:
v_type = set_req.V_RGBW
value = '00000000'
self.gateway.set_child_value(
self.node_id, self.child_id, v_type, value)
if self.gateway.optimistic:
# optimistically assume that light has changed state
self._state = False
self.update_ha_state()
@property
def available(self):
"""Return True if entity is available."""
return self.value_type in self._values
def update(self):
"""Update the controller with the latest value from a sensor."""
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
set_req = self.gateway.const.SetReq
self.battery_level = node.battery_level
for value_type, value in child.values.items():
_LOGGER.debug(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == set_req.V_LIGHT:
self._values[value_type] = (
STATE_ON if int(value) == 1 else STATE_OFF)
self._state = self._values[value_type] == STATE_ON
else:
self._values[value_type] = value
if value_type == set_req.V_DIMMER:
self._brightness = round(
255 * int(self._values[value_type]) / 100)
if self._brightness == 0:
self._state = False
if set_req.V_LIGHT not in self._values:
self._state = self._brightness > 0
if float(self.gateway.version) >= 1.5 and \
value_type in (set_req.V_RGB, set_req.V_RGBW):
# convert hex color string to rgb(w) integer list
color_list = [int(value[i:i + len(value) // 3], 16)
for i in range(0,
len(value),
len(value) // 3)]
if len(color_list) > 3:
self._white = color_list.pop()
self._rgb = color_list
if set_req.V_LIGHT not in self._values or \
set_req.V_DIMMER not in self._values:
self._state = max(color_list) > 0
+25 -12
View File
@@ -50,7 +50,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def light_update(event):
""" Callback for light updates from the RFXtrx gateway. """
if not isinstance(event.device, rfxtrxmod.LightingDevice):
if not isinstance(event.device, rfxtrxmod.LightingDevice) or \
not event.device.known_to_be_dimmable:
return
# Add entity if not exist and the automatic_add is True
@@ -74,13 +75,13 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
add_devices_callback([new_light])
# Check if entity exists or previously added automatically
if entity_id in rfxtrx.RFX_DEVICES \
and isinstance(rfxtrx.RFX_DEVICES[entity_id], RfxtrxLight):
if entity_id in rfxtrx.RFX_DEVICES:
_LOGGER.debug(
"EntityID: %s light_update. Command: %s",
entity_id,
event.values['Command']
)
if event.values['Command'] == 'On'\
or event.values['Command'] == 'Off':
@@ -90,15 +91,27 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
rfxtrx.RFX_DEVICES[entity_id]._state = is_on
rfxtrx.RFX_DEVICES[entity_id].update_ha_state()
# Fire event
if rfxtrx.RFX_DEVICES[entity_id].should_fire_event:
rfxtrx.RFX_DEVICES[entity_id].hass.bus.fire(
EVENT_BUTTON_PRESSED, {
ATTR_ENTITY_ID:
rfxtrx.RFX_DEVICES[entity_id].entity_id,
ATTR_STATE: event.values['Command'].lower()
}
)
elif event.values['Command'] == 'Set level':
# pylint: disable=protected-access
rfxtrx.RFX_DEVICES[entity_id]._brightness = \
(event.values['Dim level'] * 255 // 100)
# Update the rfxtrx device state
is_on = rfxtrx.RFX_DEVICES[entity_id]._brightness > 0
rfxtrx.RFX_DEVICES[entity_id]._state = is_on
rfxtrx.RFX_DEVICES[entity_id].update_ha_state()
else:
return
# Fire event
if rfxtrx.RFX_DEVICES[entity_id].should_fire_event:
rfxtrx.RFX_DEVICES[entity_id].hass.bus.fire(
EVENT_BUTTON_PRESSED, {
ATTR_ENTITY_ID:
rfxtrx.RFX_DEVICES[entity_id].entity_id,
ATTR_STATE: event.values['Command'].lower()
}
)
# Subscribe to main rfxtrx events
if light_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS:
+118
View File
@@ -0,0 +1,118 @@
"""
homeassistant.components.light.scsgate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for SCSGate lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.scsgate/
"""
import logging
import homeassistant.components.scsgate as scsgate
from homeassistant.components.light import Light
from homeassistant.const import ATTR_ENTITY_ID
DEPENDENCIES = ['scsgate']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Add the SCSGate swiches defined inside of the configuration file. """
devices = config.get('devices')
lights = []
logger = logging.getLogger(__name__)
if devices:
for _, entity_info in devices.items():
if entity_info['scs_id'] in scsgate.SCSGATE.devices:
continue
logger.info("Adding %s scsgate.light", entity_info['name'])
name = entity_info['name']
scs_id = entity_info['scs_id']
light = SCSGateLight(
name=name,
scs_id=scs_id,
logger=logger)
lights.append(light)
add_devices_callback(lights)
scsgate.SCSGATE.add_devices_to_register(lights)
class SCSGateLight(Light):
""" Provides a SCSGate light. """
def __init__(self, scs_id, name, logger):
self._name = name
self._scs_id = scs_id
self._toggled = False
self._logger = logger
@property
def scs_id(self):
""" SCS ID """
return self._scs_id
@property
def should_poll(self):
""" No polling needed for a SCSGate light. """
return False
@property
def name(self):
""" Returns the name of the device if any. """
return self._name
@property
def is_on(self):
""" True if light is on. """
return self._toggled
def turn_on(self, **kwargs):
""" Turn the device on. """
from scsgate.tasks import ToggleStatusTask
scsgate.SCSGATE.append_task(
ToggleStatusTask(
target=self._scs_id,
toggled=True))
self._toggled = True
self.update_ha_state()
def turn_off(self, **kwargs):
""" Turn the device off. """
from scsgate.tasks import ToggleStatusTask
scsgate.SCSGATE.append_task(
ToggleStatusTask(
target=self._scs_id,
toggled=False))
self._toggled = False
self.update_ha_state()
def process_event(self, message):
""" Handle a SCSGate message related with this light """
if self._toggled == message.toggled:
self._logger.info(
"Light %s, ignoring message %s because state already active",
self._scs_id, message)
# Nothing changed, ignoring
return
self._toggled = message.toggled
self.update_ha_state()
command = "off"
if self._toggled:
command = "on"
self.hass.bus.fire(
'button_pressed', {
ATTR_ENTITY_ID: self._scs_id,
'state': command
}
)
+81 -10
View File
@@ -9,13 +9,20 @@ https://home-assistant.io/components/light.vera/
import logging
from requests.exceptions import RequestException
from homeassistant.components.switch.vera import VeraSwitch
import homeassistant.util.dt as dt_util
from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, STATE_ON
from homeassistant.const import (
ATTR_BATTERY_LEVEL,
ATTR_TRIPPED,
ATTR_ARMED,
ATTR_LAST_TRIP_TIME,
EVENT_HOMEASSISTANT_STOP,
STATE_ON,
STATE_OFF)
REQUIREMENTS = ['pyvera==0.2.7']
REQUIREMENTS = ['pyvera==0.2.8']
_LOGGER = logging.getLogger(__name__)
@@ -67,17 +74,35 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
add_devices_callback(lights)
class VeraLight(VeraSwitch):
class VeraLight(Light):
""" Represents a Vera Light, including dimmable. """
def __init__(self, vera_device, controller, extra_data=None):
self.vera_device = vera_device
self.extra_data = extra_data
self.controller = controller
if self.extra_data and self.extra_data.get('name'):
self._name = self.extra_data.get('name')
else:
self._name = self.vera_device.name
self._state = STATE_OFF
self.controller.register(vera_device, self._update_callback)
self.update()
def _update_callback(self, _device):
self.update_ha_state(True)
@property
def state_attributes(self):
attr = super().state_attributes or {}
def name(self):
""" Get the mame of the switch. """
return self._name
@property
def brightness(self):
"""Brightness of the light."""
if self.vera_device.is_dimmable:
attr[ATTR_BRIGHTNESS] = self.vera_device.get_brightness()
return attr
return self.vera_device.get_brightness()
def turn_on(self, **kwargs):
if ATTR_BRIGHTNESS in kwargs and self.vera_device.is_dimmable:
@@ -87,3 +112,49 @@ class VeraLight(VeraSwitch):
self._state = STATE_ON
self.update_ha_state(True)
def turn_off(self, **kwargs):
self.vera_device.switch_off()
self._state = STATE_OFF
self.update_ha_state()
@property
def device_state_attributes(self):
attr = {}
if self.vera_device.has_battery:
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
if self.vera_device.is_armable:
armed = self.vera_device.is_armed
attr[ATTR_ARMED] = 'True' if armed else 'False'
if self.vera_device.is_trippable:
last_tripped = self.vera_device.last_trip
if last_tripped is not None:
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
utc_time)
else:
attr[ATTR_LAST_TRIP_TIME] = None
tripped = self.vera_device.is_tripped
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
attr['Vera Device Id'] = self.vera_device.vera_device_id
@property
def should_poll(self):
""" Tells Home Assistant not to poll this entity. """
return False
@property
def is_on(self):
""" True if device is on. """
return self._state == STATE_ON
def update(self):
""" Called by the vera device callback to update state. """
if self.vera_device.is_switched_on():
self._state = STATE_ON
else:
self._state = STATE_OFF
+32 -14
View File
@@ -8,11 +8,10 @@ https://home-assistant.io/components/light.wink/
"""
import logging
from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.components.wink import WinkToggleDevice
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.4.2']
REQUIREMENTS = ['python-wink==0.5.0']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
@@ -34,9 +33,32 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
WinkLight(light) for light in pywink.get_bulbs())
class WinkLight(WinkToggleDevice):
class WinkLight(Light):
""" Represents a Wink light. """
def __init__(self, wink):
self.wink = wink
@property
def unique_id(self):
""" Returns the id of this Wink switch. """
return "{}.{}".format(self.__class__, self.wink.device_id())
@property
def name(self):
""" Returns the name of the light if any. """
return self.wink.name()
@property
def is_on(self):
""" True if light is on. """
return self.wink.state()
@property
def brightness(self):
"""Brightness of the light."""
return int(self.wink.brightness() * 255)
# pylint: disable=too-few-public-methods
def turn_on(self, **kwargs):
""" Turns the switch on. """
@@ -48,14 +70,10 @@ class WinkLight(WinkToggleDevice):
else:
self.wink.set_state(True)
@property
def state_attributes(self):
attr = super().state_attributes
def turn_off(self):
""" Turns the switch off. """
self.wink.set_state(False)
if self.is_on:
brightness = self.wink.brightness()
if brightness is not None:
attr[ATTR_BRIGHTNESS] = int(brightness * 255)
return attr
def update(self):
""" Update state of the light. """
self.wink.update_state()
+5 -5
View File
@@ -1,9 +1,11 @@
"""
homeassistant.components.light.zigbee
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Contains functionality to use a ZigBee device as a light.
"""
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.zigbee/
"""
from homeassistant.components.light import Light
from homeassistant.components.zigbee import (
ZigBeeDigitalOut, ZigBeeDigitalOutConfig)
@@ -13,9 +15,7 @@ DEPENDENCIES = ["zigbee"]
def setup_platform(hass, config, add_entities, discovery_info=None):
"""
Create and add an entity based on the configuration.
"""
""" Create and add an entity based on the configuration. """
add_entities([
ZigBeeLight(hass, ZigBeeDigitalOutConfig(config))
])
+43 -10
View File
@@ -17,7 +17,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.const import (
STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK,
ATTR_ENTITY_ID)
from homeassistant.components import (group, wink)
from homeassistant.components import (group, verisure, wink)
DOMAIN = 'lock'
SCAN_INTERVAL = 30
@@ -28,12 +28,15 @@ ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format('all_locks')
ENTITY_ID_FORMAT = DOMAIN + '.{}'
ATTR_LOCKED = "locked"
ATTR_CODE = 'code'
ATTR_CODE_FORMAT = 'code_format'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
wink.DISCOVER_LOCKS: 'wink'
wink.DISCOVER_LOCKS: 'wink',
verisure.DISCOVER_LOCKS: 'verisure'
}
_LOGGER = logging.getLogger(__name__)
@@ -45,15 +48,25 @@ def is_locked(hass, entity_id=None):
return hass.states.is_state(entity_id, STATE_LOCKED)
def lock(hass, entity_id=None):
def lock(hass, entity_id=None, code=None):
""" Locks all or specified locks. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_LOCK, data)
def unlock(hass, entity_id=None):
def unlock(hass, entity_id=None, code=None):
""" Unlocks all or specified locks. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_UNLOCK, data)
@@ -68,11 +81,16 @@ def setup(hass, config):
""" Handles calls to the lock services. """
target_locks = component.extract_from_service(service)
if ATTR_CODE not in service.data:
code = None
else:
code = service.data[ATTR_CODE]
for item in target_locks:
if service.service == SERVICE_LOCK:
item.lock()
item.lock(code=code)
else:
item.unlock()
item.unlock(code=code)
if item.should_poll:
item.update_ha_state(True)
@@ -91,19 +109,34 @@ class LockDevice(Entity):
""" Represents a lock within Home Assistant. """
# pylint: disable=no-self-use
@property
def code_format(self):
""" regex for code format or None if no code is required. """
return None
@property
def is_locked(self):
""" Is the lock locked or unlocked. """
return None
def lock(self):
def lock(self, **kwargs):
""" Locks the lock. """
raise NotImplementedError()
def unlock(self):
def unlock(self, **kwargs):
""" Unlocks the lock. """
raise NotImplementedError()
@property
def state_attributes(self):
""" Return the state attributes. """
if self.code_format is None:
return None
state_attr = {
ATTR_CODE_FORMAT: self.code_format,
}
return state_attr
@property
def state(self):
locked = self.is_locked
+92
View File
@@ -0,0 +1,92 @@
"""
homeassistant.components.lock.verisure
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Interfaces with Verisure locks.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/verisure/
"""
import logging
import homeassistant.components.verisure as verisure
from homeassistant.components.lock import LockDevice
from homeassistant.const import (
STATE_UNKNOWN,
STATE_LOCKED, STATE_UNLOCKED)
_LOGGER = logging.getLogger(__name__)
ATTR_CODE = 'code'
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Verisure platform. """
if not verisure.MY_PAGES:
_LOGGER.error('A connection has not been made to Verisure mypages.')
return False
locks = []
locks.extend([VerisureDoorlock(value)
for value in verisure.LOCK_STATUS.values()
if verisure.SHOW_LOCKS])
add_devices(locks)
# pylint: disable=abstract-method
class VerisureDoorlock(LockDevice):
""" Represents a Verisure doorlock status. """
def __init__(self, lock_status, code=None):
self._id = lock_status.id
self._state = STATE_UNKNOWN
self._code = code
@property
def name(self):
""" Returns the name of the device. """
return 'Lock {}'.format(self._id)
@property
def state(self):
""" Returns the state of the device. """
return self._state
@property
def code_format(self):
""" Six digit code required. """
return '^\\d{%s}$' % verisure.CODE_DIGITS
def update(self):
""" Update lock status """
verisure.update_lock()
if verisure.LOCK_STATUS[self._id].status == 'unlocked':
self._state = STATE_UNLOCKED
elif verisure.LOCK_STATUS[self._id].status == 'locked':
self._state = STATE_LOCKED
elif verisure.LOCK_STATUS[self._id].status != 'pending':
_LOGGER.error(
'Unknown lock state %s',
verisure.LOCK_STATUS[self._id].status)
@property
def is_locked(self):
""" True if device is locked. """
return verisure.LOCK_STATUS[self._id].status
def unlock(self, **kwargs):
""" Send unlock command. """
verisure.MY_PAGES.lock.set(kwargs[ATTR_CODE], self._id, 'UNLOCKED')
_LOGGER.info('verisure doorlock unlocking')
verisure.MY_PAGES.lock.wait_while_pending()
verisure.update_lock()
def lock(self, **kwargs):
""" Send lock command. """
verisure.MY_PAGES.lock.set(kwargs[ATTR_CODE], self._id, 'LOCKED')
_LOGGER.info('verisure doorlock locking')
verisure.MY_PAGES.lock.wait_while_pending()
verisure.update_lock()
+1 -1
View File
@@ -11,7 +11,7 @@ import logging
from homeassistant.components.lock import LockDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.4.2']
REQUIREMENTS = ['python-wink==0.5.0']
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -32,7 +32,6 @@ DISCOVERY_PLATFORMS = {
discovery.SERVICE_PLEX: 'plex',
}
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
SERVICE_PLAY_MEDIA = 'play_media'
ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
@@ -68,14 +67,12 @@ SUPPORT_VOLUME_SET = 4
SUPPORT_VOLUME_MUTE = 8
SUPPORT_PREVIOUS_TRACK = 16
SUPPORT_NEXT_TRACK = 32
SUPPORT_YOUTUBE = 64
SUPPORT_TURN_ON = 128
SUPPORT_TURN_OFF = 256
SUPPORT_PLAY_MEDIA = 512
SUPPORT_VOLUME_STEP = 1024
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'
SERVICE_TO_METHOD = {
SERVICE_TURN_ON: 'turn_on',
SERVICE_TURN_OFF: 'turn_off',
@@ -200,6 +197,13 @@ def media_previous_track(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data)
def media_seek(hass, position, entity_id=None):
""" Send the media player the command to seek in current playing media. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
data[ATTR_MEDIA_SEEK_POSITION] = position
hass.services.call(DOMAIN, SERVICE_MEDIA_SEEK, data)
def play_media(hass, media_type, media_id, entity_id=None):
""" Send the media player the command for playing media. """
data = {"media_type": media_type, "media_id": media_id}
@@ -283,7 +287,7 @@ def setup(hass, config):
position = service.data[ATTR_MEDIA_SEEK_POSITION]
for player in target_players:
player.seek(position)
player.media_seek(position)
if player.should_poll:
player.update_ha_state(True)
@@ -291,20 +295,6 @@ def setup(hass, config):
hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service,
descriptions.get(SERVICE_MEDIA_SEEK))
def play_youtube_video_service(service, media_id=None):
""" Plays specified media_id on the media player. """
if media_id is None:
service.data.get('video')
if media_id is None:
return
for player in component.extract_from_service(service):
player.play_youtube(media_id)
if player.should_poll:
player.update_ha_state(True)
def play_media_service(service):
""" Plays specified media_id on the media player. """
media_type = service.data.get('media_type')
@@ -322,20 +312,6 @@ def setup(hass, config):
if player.should_poll:
player.update_ha_state(True)
hass.services.register(
DOMAIN, "start_fireplace",
lambda service: play_youtube_video_service(service, "eyU3bRy2x44"),
descriptions.get('start_fireplace'))
hass.services.register(
DOMAIN, "start_epic_sax",
lambda service: play_youtube_video_service(service, "kxopViU98Xo"),
descriptions.get('start_epic_sax'))
hass.services.register(
DOMAIN, SERVICE_YOUTUBE_VIDEO, play_youtube_video_service,
descriptions.get(SERVICE_YOUTUBE_VIDEO))
hass.services.register(
DOMAIN, SERVICE_PLAY_MEDIA, play_media_service,
descriptions.get(SERVICE_PLAY_MEDIA))
@@ -449,11 +425,6 @@ class MediaPlayerDevice(Entity):
""" Flags of media commands that are supported. """
return 0
@property
def device_state_attributes(self):
""" Extra attributes a device wants to expose. """
return None
def turn_on(self):
""" turn the media player on. """
raise NotImplementedError()
@@ -490,10 +461,6 @@ class MediaPlayerDevice(Entity):
""" Send seek command. """
raise NotImplementedError()
def play_youtube(self, media_id):
""" Plays a YouTube media. """
raise NotImplementedError()
def play_media(self, media_type, media_id):
""" Plays a piece of media. """
raise NotImplementedError()
@@ -529,11 +496,6 @@ class MediaPlayerDevice(Entity):
""" Boolean if next track command supported. """
return bool(self.supported_media_commands & SUPPORT_NEXT_TRACK)
@property
def support_youtube(self):
""" Boolean if YouTube is supported. """
return bool(self.supported_media_commands & SUPPORT_YOUTUBE)
@property
def support_play_media(self):
""" Boolean if play media command supported. """
@@ -579,9 +541,4 @@ class MediaPlayerDevice(Entity):
if self.media_image_url:
state_attr[ATTR_ENTITY_PICTURE] = self.media_image_url
device_attr = self.device_state_attributes
if device_attr:
state_attr.update(device_attr)
return state_attr
+22 -29
View File
@@ -16,7 +16,7 @@ from homeassistant.const import (
from homeassistant.components.media_player import (
MediaPlayerDevice,
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_YOUTUBE, SUPPORT_PLAY_MEDIA,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA,
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
@@ -25,51 +25,48 @@ CONF_IGNORE_CEC = 'ignore_cec'
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_NEXT_TRACK | SUPPORT_YOUTUBE | SUPPORT_PLAY_MEDIA
SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA
KNOWN_HOSTS = []
# pylint: disable=invalid-name
cast = None
DEFAULT_PORT = 8009
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the cast platform. """
global cast
import pychromecast
cast = pychromecast
logger = logging.getLogger(__name__)
# import CEC IGNORE attributes
ignore_cec = config.get(CONF_IGNORE_CEC, [])
if isinstance(ignore_cec, list):
cast.IGNORE_CEC += ignore_cec
pychromecast.IGNORE_CEC += ignore_cec
else:
logger.error('Chromecast conig, %s must be a list.', CONF_IGNORE_CEC)
logger.error('CEC config "%s" must be a list.', CONF_IGNORE_CEC)
hosts = []
if discovery_info and discovery_info[0] not in KNOWN_HOSTS:
hosts = [discovery_info[0]]
if discovery_info and discovery_info in KNOWN_HOSTS:
return
elif discovery_info:
hosts = [discovery_info]
elif CONF_HOST in config:
hosts = [config[CONF_HOST]]
hosts = [(config[CONF_HOST], DEFAULT_PORT)]
else:
hosts = (host_port[0] for host_port
in cast.discover_chromecasts()
if host_port[0] not in KNOWN_HOSTS)
hosts = [tuple(dev[:2]) for dev in pychromecast.discover_chromecasts()
if tuple(dev[:2]) not in KNOWN_HOSTS]
casts = []
for host in hosts:
try:
casts.append(CastDevice(host))
except cast.ChromecastConnectionError:
pass
else:
casts.append(CastDevice(*host))
KNOWN_HOSTS.append(host)
except pychromecast.ChromecastConnectionError:
pass
add_devices(casts)
@@ -80,11 +77,9 @@ class CastDevice(MediaPlayerDevice):
# pylint: disable=abstract-method
# pylint: disable=too-many-public-methods
def __init__(self, host):
import pychromecast.controllers.youtube as youtube
self.cast = cast.Chromecast(host)
self.youtube = youtube.YouTubeController()
self.cast.register_handler(self.youtube)
def __init__(self, host, port):
import pychromecast
self.cast = pychromecast.Chromecast(host, port)
self.cast.socket_client.receiver_controller.register_status_listener(
self)
@@ -224,11 +219,13 @@ class CastDevice(MediaPlayerDevice):
""" Turns on the ChromeCast. """
# The only way we can turn the Chromecast is on is by launching an app
if not self.cast.status or not self.cast.status.is_active_input:
import pychromecast
if self.cast.app_id:
self.cast.quit_app()
self.cast.play_media(
CAST_SPLASH, cast.STREAM_TYPE_BUFFERED)
CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED)
def turn_off(self):
""" Turns Chromecast off. """
@@ -266,10 +263,6 @@ class CastDevice(MediaPlayerDevice):
""" Plays media from a URL """
self.cast.media_controller.play_media(media_id, media_type)
def play_youtube(self, media_id):
""" Plays a YouTube media. """
self.youtube.play_video(media_id)
# implementation of chromecast status_listener methods
def new_cast_status(self, status):
@@ -7,11 +7,11 @@ from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
from homeassistant.components.media_player import (
MediaPlayerDevice, YOUTUBE_COVER_URL_FORMAT,
MediaPlayerDevice,
MEDIA_TYPE_VIDEO, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_YOUTUBE,
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PREVIOUS_TRACK,
SUPPORT_NEXT_TRACK)
SUPPORT_NEXT_TRACK, SUPPORT_PLAY_MEDIA)
# pylint: disable=unused-argument
@@ -26,9 +26,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
])
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'
YOUTUBE_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_YOUTUBE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA
MUSIC_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
@@ -150,10 +152,9 @@ class DemoYoutubePlayer(AbstractDemoPlayer):
""" Flags of media commands that are supported. """
return YOUTUBE_PLAYER_SUPPORT
def play_youtube(self, media_id):
""" Plays a YouTube media. """
def play_media(self, media_type, media_id):
""" Plays a piece of media. """
self.youtube_id = media_id
self._media_title = 'some YouTube video'
self.update_ha_state()
@@ -234,7 +235,7 @@ class DemoMusicPlayer(AbstractDemoPlayer):
""" Flags of media commands that are supported. """
support = MUSIC_PLAYER_SUPPORT
if self._cur_track > 1:
if self._cur_track > 0:
support |= SUPPORT_PREVIOUS_TRACK
if self._cur_track < len(self.tracks)-1:
@@ -24,7 +24,6 @@ SUPPORT_DENON = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Denon platform. """
if not config.get(CONF_HOST):
@@ -48,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DenonDevice(MediaPlayerDevice):
""" Represents a Denon device. """
# pylint: disable=too-many-public-methods
# pylint: disable=too-many-public-methods, abstract-method
def __init__(self, name, host):
self._name = name
@@ -145,10 +144,6 @@ class DenonDevice(MediaPlayerDevice):
""" mute (true) or unmute (false) media player. """
self.telnet_command("MU" + ("ON" if mute else "OFF"))
def media_play_pause(self):
""" media_play_pause media player. """
raise NotImplementedError()
def media_play(self):
""" media_play media player. """
self.telnet_command("NS9A")
@@ -164,9 +159,6 @@ class DenonDevice(MediaPlayerDevice):
def media_previous_track(self):
self.telnet_command("NS9E")
def media_seek(self, position):
raise NotImplementedError()
def turn_on(self):
""" turn the media player on. """
self.telnet_command("PWON")
@@ -105,6 +105,8 @@ class FireTV(object):
class FireTVDevice(MediaPlayerDevice):
""" Represents an Amazon Fire TV device on the network. """
# pylint: disable=abstract-method
def __init__(self, host, device, name):
self._firetv = FireTV(host, device)
self._name = name
@@ -176,15 +178,3 @@ class FireTVDevice(MediaPlayerDevice):
def media_next_track(self):
""" Send next track command (results in fast-forward). """
self._firetv.action('media_next')
def media_seek(self, position):
raise NotImplementedError()
def mute_volume(self, mute):
raise NotImplementedError()
def play_youtube(self, media_id):
raise NotImplementedError()
def set_volume_level(self, volume):
raise NotImplementedError()
+21 -22
View File
@@ -22,43 +22,41 @@ SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the kodi platform. """
url = '{}:{}'.format(config.get('host'), config.get('port', '8080'))
jsonrpc_url = config.get('url') # deprecated
if jsonrpc_url:
url = jsonrpc_url.rstrip('/jsonrpc')
add_devices([
KodiDevice(
config.get('name', 'Kodi'),
config.get('url'),
url,
auth=(
config.get('user', ''),
config.get('password', ''))),
])
def _get_image_url(kodi_url):
""" Helper function that parses the thumbnail URLs used by Kodi. """
url_components = urllib.parse.urlparse(kodi_url)
if url_components.scheme == 'image':
return urllib.parse.unquote(url_components.netloc)
class KodiDevice(MediaPlayerDevice):
""" Represents a XBMC/Kodi device. """
# pylint: disable=too-many-public-methods
# pylint: disable=too-many-public-methods, abstract-method
def __init__(self, name, url, auth=None):
import jsonrpc_requests
self._name = name
self._url = url
self._server = jsonrpc_requests.Server(url, auth=auth)
self._server = jsonrpc_requests.Server(
'{}/jsonrpc'.format(self._url),
auth=auth)
self._players = None
self._properties = None
self._item = None
self._app_properties = None
self.update()
@property
@@ -156,7 +154,16 @@ class KodiDevice(MediaPlayerDevice):
def media_image_url(self):
""" Image url of current playing media. """
if self._item is not None:
return _get_image_url(self._item['thumbnail'])
return self._get_image_url()
def _get_image_url(self):
""" Helper function that parses the thumbnail URLs used by Kodi. """
url_components = urllib.parse.urlparse(self._item['thumbnail'])
if url_components.scheme == 'image':
return '{}/image/{}'.format(
self._url,
urllib.parse.quote_plus(self._item['thumbnail']))
@property
def media_title(self):
@@ -263,11 +270,3 @@ class KodiDevice(MediaPlayerDevice):
self._server.Player.Seek(players[0]['playerid'], time)
self.update_ha_state()
def turn_on(self):
""" turn the media player on. """
raise NotImplementedError()
def play_youtube(self, media_id):
""" Plays a YouTube media. """
raise NotImplementedError()
@@ -59,7 +59,7 @@ def config_from_file(filename, config=None):
return {}
# pylint: disable=abstract-method, unused-argument
# pylint: disable=abstract-method
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Sets up the plex platform. """
@@ -0,0 +1,170 @@
"""
homeassistant.components.media_player.denon
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to Samsung TV with a Laninterface.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.samsungtv/
"""
import logging
import socket
from homeassistant.components.media_player import (
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_VOLUME_STEP,
SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_NEXT_TRACK, SUPPORT_TURN_OFF,
DOMAIN)
from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF,
STATE_ON, STATE_UNKNOWN)
from homeassistant.helpers import validate_config
CONF_PORT = "port"
CONF_TIMEOUT = "timeout"
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['samsungctl==0.5.1']
SUPPORT_SAMSUNGTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_NEXT_TRACK | SUPPORT_TURN_OFF
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Samsung TV platform. """
# Validate that all required config options are given
if not validate_config({DOMAIN: config}, {DOMAIN: [CONF_HOST]}, _LOGGER):
return False
# Default the entity_name to 'Samsung TV Remote'
name = config.get(CONF_NAME, 'Samsung TV Remote')
# Generate a config for the Samsung lib
remote_config = {
"name": "HomeAssistant",
"description": config.get(CONF_NAME, ''),
"id": "ha.component.samsung",
"port": config.get(CONF_PORT, 55000),
"host": config.get(CONF_HOST),
"timeout": config.get(CONF_TIMEOUT, 0),
}
add_devices([SamsungTVDevice(name, remote_config)])
# pylint: disable=abstract-method
class SamsungTVDevice(MediaPlayerDevice):
""" Represents a Samsung TV. """
# pylint: disable=too-many-public-methods
def __init__(self, name, config):
from samsungctl import Remote
# Save a reference to the imported class
self._remote_class = Remote
self._name = name
# Assume that the TV is not muted
self._muted = False
# Assume that the TV is in Play mode
self._playing = True
self._state = STATE_UNKNOWN
self._remote = None
self._config = config
def update(self):
# Send an empty key to see if we are still connected
return self.send_key('KEY_POWER')
def get_remote(self):
""" Creates or Returns a remote control instance """
if self._remote is None:
# We need to create a new instance to reconnect.
self._remote = self._remote_class(self._config)
return self._remote
def send_key(self, key):
""" Sends a key to the tv and handles exceptions """
try:
self.get_remote().control(key)
self._state = STATE_ON
except (self._remote_class.UnhandledResponse,
self._remote_class.AccessDenied, BrokenPipeError):
# We got a response so it's on.
# BrokenPipe can occur when the commands is sent to fast
self._state = STATE_ON
self._remote = None
return False
except (self._remote_class.ConnectionClosed, socket.timeout,
TimeoutError, OSError):
self._state = STATE_OFF
self._remote = None
return False
return True
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
return self._state
@property
def is_volume_muted(self):
""" Boolean if volume is currently muted. """
return self._muted
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_SAMSUNGTV
def turn_off(self):
""" turn_off media player. """
self.send_key("KEY_POWEROFF")
def volume_up(self):
""" volume_up media player. """
self.send_key("KEY_VOLUP")
def volume_down(self):
""" volume_down media player. """
self.send_key("KEY_VOLDOWN")
def mute_volume(self, mute):
self.send_key("KEY_MUTE")
def media_play_pause(self):
""" Simulate play pause media player. """
if self._playing:
self.media_pause()
else:
self.media_play()
def media_play(self):
""" media_play media player. """
self._playing = True
self.send_key("KEY_PLAY")
def media_pause(self):
""" media_pause media player. """
self._playing = False
self.send_key("KEY_PAUSE")
def media_next_track(self):
""" Send next track command. """
self.send_key("KEY_FF")
def media_previous_track(self):
self.send_key("KEY_REWIND")
def turn_on(self):
""" turn the media player on. """
self.send_key("KEY_POWERON")
@@ -0,0 +1,85 @@
"""
homeassistant.components.media_player.snapcast
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to interact with Snapcast clients.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.snapcast/
"""
import logging
import socket
from homeassistant.const import (
STATE_ON, STATE_OFF)
from homeassistant.components.media_player import (
MediaPlayerDevice,
SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE)
SUPPORT_SNAPCAST = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE
DOMAIN = 'snapcast'
REQUIREMENTS = ['snapcast==1.1.1']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Snapcast platform. """
import snapcast.control
host = config.get('host')
port = config.get('port', snapcast.control.CONTROL_PORT)
if not host:
_LOGGER.error('No snapserver host specified')
return
try:
server = snapcast.control.Snapserver(host, port)
except socket.gaierror:
_LOGGER.error('Could not connect to Snapcast server at %s:%d',
host, port)
return
add_devices([SnapcastDevice(client) for client in server.clients])
class SnapcastDevice(MediaPlayerDevice):
""" Represents a Snapcast client device. """
# pylint: disable=abstract-method
def __init__(self, client):
self._client = client
@property
def name(self):
""" Device name. """
return self._client.identifier
@property
def volume_level(self):
""" Volume level. """
return self._client.volume / 100
@property
def is_volume_muted(self):
""" Volume muted. """
return self._client.muted
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_SNAPCAST
@property
def state(self):
""" State of the player. """
if self._client.connected:
return STATE_ON
return STATE_OFF
def mute_volume(self, mute):
""" Mute status. """
self._client.muted = mute
def set_volume_level(self, volume):
""" Volume level. """
self._client.volume = round(volume * 100)
@@ -27,7 +27,6 @@ SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \
SUPPORT_SEEK | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the squeezebox platform. """
if not config.get(CONF_HOST):
@@ -138,7 +137,7 @@ class LogitechMediaServer(object):
class SqueezeBoxDevice(MediaPlayerDevice):
""" Represents a SqueezeBox device. """
# pylint: disable=too-many-arguments
# pylint: disable=too-many-arguments, abstract-method
def __init__(self, lms, player_id):
super(SqueezeBoxDevice, self).__init__()
self._lms = lms
@@ -292,7 +291,3 @@ class SqueezeBoxDevice(MediaPlayerDevice):
""" turn the media player on. """
self._lms.query(self._id, 'power', '1')
self.update_ha_state()
def play_youtube(self, media_id):
""" Plays a YouTube media. """
raise NotImplementedError()
@@ -27,7 +27,7 @@ from homeassistant.components.media_player import (
MediaPlayerDevice, DOMAIN,
SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
SERVICE_PLAY_MEDIA, SERVICE_YOUTUBE_VIDEO,
SERVICE_PLAY_MEDIA,
ATTR_SUPPORTED_MEDIA_COMMANDS, ATTR_MEDIA_VOLUME_MUTED,
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_DURATION,
ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST, ATTR_MEDIA_ALBUM_NAME,
@@ -215,15 +215,6 @@ class UniversalMediaPlayer(MediaPlayerDevice):
else:
return None
def _cache_active_child_state(self):
""" The state of the active child or None """
for child_name in self._children:
child_state = self.hass.states.get(child_name)
if child_state and child_state.state not in OFF_STATES:
self._child_state = child_state
return
self._child_state = None
@property
def name(self):
""" name of universal player """
@@ -406,11 +397,6 @@ class UniversalMediaPlayer(MediaPlayerDevice):
data = {ATTR_MEDIA_SEEK_POSITION: position}
self._call_service(SERVICE_MEDIA_SEEK, data)
def play_youtube(self, media_id):
""" Plays a YouTube media. """
data = {'media_id': media_id}
self._call_service(SERVICE_YOUTUBE_VIDEO, data)
def play_media(self, media_type, media_id):
""" Plays a piece of media. """
data = {'media_type': media_type, 'media_id': media_id}
+39 -8
View File
@@ -12,8 +12,10 @@ import socket
import time
from homeassistant.config import load_yaml_config_file
from homeassistant.exceptions import HomeAssistantError
import homeassistant.util as util
from homeassistant.util import template
from homeassistant.helpers import validate_config
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
@@ -49,24 +51,34 @@ DEFAULT_PROTOCOL = PROTOCOL_311
ATTR_TOPIC = 'topic'
ATTR_PAYLOAD = 'payload'
ATTR_PAYLOAD_TEMPLATE = 'payload_template'
ATTR_QOS = 'qos'
ATTR_RETAIN = 'retain'
MAX_RECONNECT_WAIT = 300 # seconds
def publish(hass, topic, payload, qos=None, retain=None):
"""Publish message to an MQTT topic."""
data = {
ATTR_TOPIC: topic,
ATTR_PAYLOAD: payload,
}
def _build_publish_data(topic, qos, retain):
"""Build the arguments for the publish service without the payload."""
data = {ATTR_TOPIC: topic}
if qos is not None:
data[ATTR_QOS] = qos
if retain is not None:
data[ATTR_RETAIN] = retain
return data
def publish(hass, topic, payload, qos=None, retain=None):
"""Publish message to an MQTT topic."""
data = _build_publish_data(topic, qos, retain)
data[ATTR_PAYLOAD] = payload
hass.services.call(DOMAIN, SERVICE_PUBLISH, data)
def publish_template(hass, topic, payload_template, qos=None, retain=None):
"""Publish message to an MQTT topic using a template payload."""
data = _build_publish_data(topic, qos, retain)
data[ATTR_PAYLOAD_TEMPLATE] = payload_template
hass.services.call(DOMAIN, SERVICE_PUBLISH, data)
@@ -132,15 +144,34 @@ def setup(hass, config):
"""Handle MQTT publish service calls."""
msg_topic = call.data.get(ATTR_TOPIC)
payload = call.data.get(ATTR_PAYLOAD)
payload_template = call.data.get(ATTR_PAYLOAD_TEMPLATE)
qos = call.data.get(ATTR_QOS, DEFAULT_QOS)
retain = call.data.get(ATTR_RETAIN, DEFAULT_RETAIN)
if payload is None:
if payload_template is None:
_LOGGER.error(
"You must set either '%s' or '%s' to use this service",
ATTR_PAYLOAD, ATTR_PAYLOAD_TEMPLATE)
return
try:
payload = template.render(hass, payload_template)
except template.jinja2.TemplateError as exc:
_LOGGER.error(
"Unable to publish to '%s': rendering payload template of "
"'%s' failed because %s.",
msg_topic, payload_template, exc)
return
if msg_topic is None or payload is None:
return
MQTT_CLIENT.publish(msg_topic, payload, qos, retain)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mqtt)
hass.services.register(DOMAIN, SERVICE_PUBLISH, publish_service)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_PUBLISH, publish_service,
descriptions.get(SERVICE_PUBLISH))
return True
@@ -0,0 +1,29 @@
publish:
description: Publish a message to an MQTT topic
fields:
topic:
description: Topic to publish payload
example: /homeassistant/hello
payload:
description: Payload to publish
example: This is great
payload_template:
description: Template to render as payload value. Ignored if payload given.
example: "{{ states('sensor.temperature') }}"
qos:
description: Quality of Service
example: 2
values:
- 0
- 1
- 2
default: 0
retain:
description: If message should have the retain flag set.
example: true
default: false
+2 -1
View File
@@ -11,6 +11,7 @@ from homeassistant.core import EventOrigin, State
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
from homeassistant.components.mqtt import SERVICE_PUBLISH as MQTT_SVC_PUBLISH
from homeassistant.const import (
ATTR_SERVICE_DATA,
MATCH_ALL,
EVENT_TIME_CHANGED,
EVENT_CALL_SERVICE,
@@ -46,7 +47,7 @@ def setup(hass, config):
if (
event.data.get('domain') == MQTT_DOMAIN and
event.data.get('service') == MQTT_SVC_PUBLISH and
event.data.get('topic') == pub_topic
event.data[ATTR_SERVICE_DATA].get('topic') == pub_topic
):
return
+26 -10
View File
@@ -1,5 +1,6 @@
"""
homeassistant.components.mysensors
homeassistant.components.mysensors.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MySensors component that connects to a MySensors gateway via pymysensors
API.
@@ -24,13 +25,16 @@ CONF_DEBUG = 'debug'
CONF_PERSISTENCE = 'persistence'
CONF_PERSISTENCE_FILE = 'persistence_file'
CONF_VERSION = 'version'
CONF_BAUD_RATE = 'baud_rate'
CONF_OPTIMISTIC = 'optimistic'
DEFAULT_VERSION = '1.4'
DEFAULT_BAUD_RATE = 115200
DOMAIN = 'mysensors'
DEPENDENCIES = []
REQUIREMENTS = [
'https://github.com/theolind/pymysensors/archive/'
'005bff4c5ca7a56acd30e816bc3bcdb5cb2d46fd.zip#pymysensors==0.4']
'f0c928532167fb24823efa793ec21ca646fd37a6.zip#pymysensors==0.5']
_LOGGER = logging.getLogger(__name__)
ATTR_NODE_ID = 'node_id'
ATTR_CHILD_ID = 'child_id'
@@ -38,13 +42,15 @@ ATTR_PORT = 'port'
GATEWAYS = None
DISCOVER_SENSORS = "mysensors.sensors"
DISCOVER_SWITCHES = "mysensors.switches"
DISCOVER_SENSORS = 'mysensors.sensors'
DISCOVER_SWITCHES = 'mysensors.switches'
DISCOVER_LIGHTS = 'mysensors.lights'
# Maps discovered services to their platforms
DISCOVERY_COMPONENTS = [
('sensor', DISCOVER_SENSORS),
('switch', DISCOVER_SWITCHES),
('light', DISCOVER_LIGHTS),
]
@@ -54,21 +60,28 @@ def setup(hass, config):
{DOMAIN: [CONF_GATEWAYS]},
_LOGGER):
return False
if not all(CONF_PORT in gateway
for gateway in config[DOMAIN][CONF_GATEWAYS]):
_LOGGER.error('Missing required configuration items '
'in %s: %s', DOMAIN, CONF_PORT)
return False
import mysensors.mysensors as mysensors
version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION))
is_metric = (hass.config.temperature_unit == TEMP_CELCIUS)
def setup_gateway(port, persistence, persistence_file, version):
def setup_gateway(port, persistence, persistence_file, version, baud_rate):
"""Return gateway after setup of the gateway."""
gateway = mysensors.SerialGateway(port, event_callback=None,
persistence=persistence,
persistence_file=persistence_file,
protocol_version=version)
protocol_version=version,
baud=baud_rate)
gateway.metric = is_metric
gateway.debug = config[DOMAIN].get(CONF_DEBUG, False)
gateway = GatewayWrapper(gateway, version)
optimistic = config[DOMAIN].get(CONF_OPTIMISTIC, False)
gateway = GatewayWrapper(gateway, version, optimistic)
# pylint: disable=attribute-defined-outside-init
gateway.event_callback = gateway.callback_factory()
@@ -98,8 +111,9 @@ def setup(hass, config):
persistence_file = gway.get(
CONF_PERSISTENCE_FILE,
hass.config.path('mysensors{}.pickle'.format(index + 1)))
baud_rate = gway.get(CONF_BAUD_RATE, DEFAULT_BAUD_RATE)
GATEWAYS[port] = setup_gateway(
port, persistence, persistence_file, version)
port, persistence, persistence_file, version, baud_rate)
for (component, discovery_service) in DISCOVERY_COMPONENTS:
# Ensure component is loaded
@@ -145,12 +159,13 @@ def pf_callback_factory(map_sv_types, devices, add_devices, entity_class):
class GatewayWrapper(object):
"""Gateway wrapper class, by subclassing serial gateway."""
def __init__(self, gateway, version):
def __init__(self, gateway, version, optimistic):
"""Setup class attributes on instantiation.
Args:
gateway (mysensors.SerialGateway): Gateway to wrap.
version (str): Version of mysensors API.
optimistic (bool): Send values to actuators without feedback state.
Attributes:
_wrapped_gateway (mysensors.SerialGateway): Wrapped gateway.
@@ -163,6 +178,7 @@ class GatewayWrapper(object):
self.version = version
self.platform_callbacks = []
self.const = self.get_const()
self.optimistic = optimistic
self.__initialised = True
def __getattr__(self, name):
@@ -195,7 +211,7 @@ class GatewayWrapper(object):
"""Return a new callback function."""
def node_update(update_type, node_id):
"""Callback for node updates from the MySensors gateway."""
_LOGGER.info('update %s: node %s', update_type, node_id)
_LOGGER.debug('update %s: node %s', update_type, node_id)
for callback in self.platform_callbacks:
callback(self, node_id)
+80
View File
@@ -0,0 +1,80 @@
"""
homeassistant.components.notify.rest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
REST platform for notify component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.rest/
"""
import logging
import requests
from homeassistant.helpers import validate_config
from homeassistant.components.notify import (
DOMAIN, ATTR_TITLE, ATTR_TARGET, BaseNotificationService)
_LOGGER = logging.getLogger(__name__)
DEFAULT_METHOD = 'GET'
DEFAULT_MESSAGE_PARAM_NAME = 'message'
DEFAULT_TITLE_PARAM_NAME = None
DEFAULT_TARGET_PARAM_NAME = None
def get_service(hass, config):
""" Get the REST notification service. """
if not validate_config({DOMAIN: config},
{DOMAIN: ['resource', ]},
_LOGGER):
return None
method = config.get('method', DEFAULT_METHOD)
message_param_name = config.get('message_param_name',
DEFAULT_MESSAGE_PARAM_NAME)
title_param_name = config.get('title_param_name',
DEFAULT_TITLE_PARAM_NAME)
target_param_name = config.get('target_param_name',
DEFAULT_TARGET_PARAM_NAME)
return RestNotificationService(config['resource'], method,
message_param_name, title_param_name,
target_param_name)
# pylint: disable=too-few-public-methods, too-many-arguments
class RestNotificationService(BaseNotificationService):
""" Implements notification service for REST. """
def __init__(self, resource, method, message_param_name,
title_param_name, target_param_name):
self._resource = resource
self._method = method.upper()
self._message_param_name = message_param_name
self._title_param_name = title_param_name
self._target_param_name = target_param_name
def send_message(self, message="", **kwargs):
""" Send a message to a user. """
data = {
self._message_param_name: message
}
if self._title_param_name is not None:
data[self._title_param_name] = kwargs.get(ATTR_TITLE)
if self._target_param_name is not None:
data[self._title_param_name] = kwargs.get(ATTR_TARGET)
if self._method == 'POST':
response = requests.post(self._resource, data=data, timeout=10)
elif self._method == 'POST_JSON':
response = requests.post(self._resource, json=data, timeout=10)
else: # default GET
response = requests.get(self._resource, params=data, timeout=10)
if response.status_code not in (200, 201):
_LOGGER.exception(
"Error sending message. Response %d: %s:",
response.status_code, response.reason)
+238
View File
@@ -0,0 +1,238 @@
"""
homeassistant.components.proximity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to monitor the proximity of devices to a particular zone and the
direction of travel.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/proximity/
"""
import logging
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.entity import Entity
from homeassistant.util.location import distance
DEPENDENCIES = ['zone', 'device_tracker']
DOMAIN = 'proximity'
# Default tolerance
DEFAULT_TOLERANCE = 1
# Default zone
DEFAULT_PROXIMITY_ZONE = 'home'
# Entity attributes
ATTR_DIST_FROM = 'dist_to_zone'
ATTR_DIR_OF_TRAVEL = 'dir_of_travel'
ATTR_NEAREST = 'nearest'
ATTR_FRIENDLY_NAME = 'friendly_name'
_LOGGER = logging.getLogger(__name__)
def setup(hass, config): # pylint: disable=too-many-locals,too-many-statements
""" Get the zones and offsets from configuration.yaml. """
ignored_zones = []
if 'ignored_zones' in config[DOMAIN]:
for variable in config[DOMAIN]['ignored_zones']:
ignored_zones.append(variable)
# Get the devices from configuration.yaml
if 'devices' not in config[DOMAIN]:
_LOGGER.error('devices not found in config')
return False
proximity_devices = []
for variable in config[DOMAIN]['devices']:
proximity_devices.append(variable)
# Get the direction of travel tolerance from configuration.yaml
tolerance = config[DOMAIN].get('tolerance', DEFAULT_TOLERANCE)
# Get the zone to monitor proximity to from configuration.yaml
proximity_zone = config[DOMAIN].get('zone', DEFAULT_PROXIMITY_ZONE)
entity_id = DOMAIN + '.' + proximity_zone
proximity_zone = 'zone.' + proximity_zone
state = hass.states.get(proximity_zone)
zone_friendly_name = (state.name).lower()
# set the default values
dist_to_zone = 'not set'
dir_of_travel = 'not set'
nearest = 'not set'
proximity = Proximity(hass, zone_friendly_name, dist_to_zone,
dir_of_travel, nearest, ignored_zones,
proximity_devices, tolerance, proximity_zone)
proximity.entity_id = entity_id
proximity.update_ha_state()
# Main command to monitor proximity of devices
track_state_change(hass, proximity_devices,
proximity.check_proximity_state_change)
return True
class Proximity(Entity): # pylint: disable=too-many-instance-attributes
""" Represents a Proximity. """
def __init__(self, hass, zone_friendly_name, dist_to, dir_of_travel,
nearest, ignored_zones, proximity_devices, tolerance,
proximity_zone):
# pylint: disable=too-many-arguments
self.hass = hass
self.friendly_name = zone_friendly_name
self.dist_to = dist_to
self.dir_of_travel = dir_of_travel
self.nearest = nearest
self.ignored_zones = ignored_zones
self.proximity_devices = proximity_devices
self.tolerance = tolerance
self.proximity_zone = proximity_zone
@property
def state(self):
""" Returns the state. """
return self.dist_to
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity. """
return "km"
@property
def state_attributes(self):
""" Returns the state attributes. """
return {
ATTR_DIR_OF_TRAVEL: self.dir_of_travel,
ATTR_NEAREST: self.nearest,
ATTR_FRIENDLY_NAME: self.friendly_name
}
def check_proximity_state_change(self, entity, old_state, new_state):
# pylint: disable=too-many-branches,too-many-statements,too-many-locals
""" Function to perform the proximity checking. """
entity_name = new_state.name
devices_to_calculate = False
devices_in_zone = ''
zone_state = self.hass.states.get(self.proximity_zone)
proximity_latitude = zone_state.attributes.get('latitude')
proximity_longitude = zone_state.attributes.get('longitude')
# Check for devices in the monitored zone
for device in self.proximity_devices:
device_state = self.hass.states.get(device)
if device_state.state not in self.ignored_zones:
devices_to_calculate = True
# Check the location of all devices
if (device_state.state).lower() == (self.friendly_name).lower():
device_friendly = device_state.name
if devices_in_zone != '':
devices_in_zone = devices_in_zone + ', '
devices_in_zone = devices_in_zone + device_friendly
# No-one to track so reset the entity
if not devices_to_calculate:
self.dist_to = 'not set'
self.dir_of_travel = 'not set'
self.nearest = 'not set'
self.update_ha_state()
return
# At least one device is in the monitored zone so update the entity
if devices_in_zone != '':
self.dist_to = 0
self.dir_of_travel = 'arrived'
self.nearest = devices_in_zone
self.update_ha_state()
return
# We can't check proximity because latitude and longitude don't exist
if 'latitude' not in new_state.attributes:
return
# Collect distances to the zone for all devices
distances_to_zone = {}
for device in self.proximity_devices:
# Ignore devices in an ignored zone
device_state = self.hass.states.get(device)
if device_state.state in self.ignored_zones:
continue
# Ignore devices if proximity cannot be calculated
if 'latitude' not in device_state.attributes:
continue
# Calculate the distance to the proximity zone
dist_to_zone = distance(proximity_latitude,
proximity_longitude,
device_state.attributes['latitude'],
device_state.attributes['longitude'])
# Add the device and distance to a dictionary
distances_to_zone[device] = round(dist_to_zone / 1000, 1)
# Loop through each of the distances collected and work out the closest
closest_device = ''
dist_to_zone = 1000000
for device in distances_to_zone:
if distances_to_zone[device] < dist_to_zone:
closest_device = device
dist_to_zone = distances_to_zone[device]
# If the closest device is one of the other devices
if closest_device != entity:
self.dist_to = round(distances_to_zone[closest_device])
self.dir_of_travel = 'unknown'
device_state = self.hass.states.get(closest_device)
self.nearest = device_state.name
self.update_ha_state()
return
# Stop if we cannot calculate the direction of travel (i.e. we don't
# have a previous state and a current LAT and LONG)
if old_state is None or 'latitude' not in old_state.attributes:
self.dist_to = round(distances_to_zone[entity])
self.dir_of_travel = 'unknown'
self.nearest = entity_name
self.update_ha_state()
return
# Reset the variables
distance_travelled = 0
# Calculate the distance travelled
old_distance = distance(proximity_latitude, proximity_longitude,
old_state.attributes['latitude'],
old_state.attributes['longitude'])
new_distance = distance(proximity_latitude, proximity_longitude,
new_state.attributes['latitude'],
new_state.attributes['longitude'])
distance_travelled = round(new_distance - old_distance, 1)
# Check for tolerance
if distance_travelled < self.tolerance * -1:
direction_of_travel = 'towards'
elif distance_travelled > self.tolerance:
direction_of_travel = 'away_from'
else:
direction_of_travel = 'stationary'
# Update the proximity entity
self.dist_to = round(dist_to_zone)
self.dir_of_travel = direction_of_travel
self.nearest = entity_name
self.update_ha_state()
_LOGGER.debug('proximity.%s update entity: distance=%s: direction=%s: '
'device=%s', self.friendly_name, round(dist_to_zone),
direction_of_travel, entity_name)
_LOGGER.info('%s: proximity calculation complete', entity_name)
+61 -21
View File
@@ -226,22 +226,28 @@ class Recorder(threading.Thread):
# State got deleted
if state is None:
state_state = ''
state_domain = ''
state_attr = '{}'
last_changed = last_updated = now
else:
state_domain = state.domain
state_state = state.state
state_attr = json.dumps(state.attributes)
state_attr = json.dumps(dict(state.attributes))
last_changed = state.last_changed
last_updated = state.last_updated
info = (
entity_id, state_state, state_attr, last_changed, last_updated,
entity_id, state_domain, state_state, state_attr,
last_changed, last_updated,
now, self.utc_offset, event_id)
self.query(
"INSERT INTO states ("
"entity_id, state, attributes, last_changed, last_updated,"
"created, utc_offset, event_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
"""
INSERT INTO states (
entity_id, domain, state, attributes, last_changed, last_updated,
created, utc_offset, event_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
info)
def record_event(self, event):
@@ -279,7 +285,8 @@ class Recorder(threading.Thread):
else:
return cur.fetchall()
except sqlite3.IntegrityError:
except (sqlite3.IntegrityError, sqlite3.OperationalError,
sqlite3.ProgrammingError):
_LOGGER.exception(
"Error querying the database using: %s", sql_query)
return []
@@ -323,7 +330,7 @@ class Recorder(threading.Thread):
migration_id = 0
if migration_id < 1:
self.query("""
cur.execute("""
CREATE TABLE recorder_runs (
run_id integer primary key,
start integer,
@@ -332,7 +339,7 @@ class Recorder(threading.Thread):
created integer)
""")
self.query("""
cur.execute("""
CREATE TABLE events (
event_id integer primary key,
event_type text,
@@ -340,10 +347,10 @@ class Recorder(threading.Thread):
origin text,
created integer)
""")
self.query(
cur.execute(
'CREATE INDEX events__event_type ON events(event_type)')
self.query("""
cur.execute("""
CREATE TABLE states (
state_id integer primary key,
entity_id text,
@@ -353,57 +360,90 @@ class Recorder(threading.Thread):
last_updated integer,
created integer)
""")
self.query('CREATE INDEX states__entity_id ON states(entity_id)')
cur.execute('CREATE INDEX states__entity_id ON states(entity_id)')
save_migration(1)
if migration_id < 2:
self.query("""
cur.execute("""
ALTER TABLE events
ADD COLUMN time_fired integer
""")
self.query('UPDATE events SET time_fired=created')
cur.execute('UPDATE events SET time_fired=created')
save_migration(2)
if migration_id < 3:
utc_offset = self.utc_offset
self.query("""
cur.execute("""
ALTER TABLE recorder_runs
ADD COLUMN utc_offset integer
""")
self.query("""
cur.execute("""
ALTER TABLE events
ADD COLUMN utc_offset integer
""")
self.query("""
cur.execute("""
ALTER TABLE states
ADD COLUMN utc_offset integer
""")
self.query("UPDATE recorder_runs SET utc_offset=?", [utc_offset])
self.query("UPDATE events SET utc_offset=?", [utc_offset])
self.query("UPDATE states SET utc_offset=?", [utc_offset])
cur.execute("UPDATE recorder_runs SET utc_offset=?", [utc_offset])
cur.execute("UPDATE events SET utc_offset=?", [utc_offset])
cur.execute("UPDATE states SET utc_offset=?", [utc_offset])
save_migration(3)
if migration_id < 4:
# We had a bug where we did not save utc offset for recorder runs
self.query(
cur.execute(
"""UPDATE recorder_runs SET utc_offset=?
WHERE utc_offset IS NULL""", [self.utc_offset])
self.query("""
cur.execute("""
ALTER TABLE states
ADD COLUMN event_id integer
""")
save_migration(4)
if migration_id < 5:
# Add domain so that thermostat graphs look right
try:
cur.execute("""
ALTER TABLE states
ADD COLUMN domain text
""")
except sqlite3.OperationalError:
# We had a bug in this migration for a while on dev
# Without this, dev-users will have to throw away their db
pass
# TravisCI has Python compiled against an old version of SQLite3
# which misses the instr method.
self.conn.create_function(
"instr", 2,
lambda string, substring: string.find(substring) + 1)
# populate domain with defaults
cur.execute("""
UPDATE states
set domain=substr(entity_id, 0, instr(entity_id, '.'))
""")
# add indexes we are going to use a lot on selects
cur.execute("""
CREATE INDEX states__state_changes ON
states (last_changed, last_updated, entity_id)""")
cur.execute("""
CREATE INDEX states__significant_changes ON
states (domain, last_updated, entity_id)""")
save_migration(5)
def _close_connection(self):
""" Close connection to the database. """
_LOGGER.info("Closing database")
+4 -2
View File
@@ -9,8 +9,8 @@ https://home-assistant.io/components/rfxtrx/
import logging
from homeassistant.util import slugify
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/0.2.zip' +
'#RFXtrx==0.2']
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/0.4.zip' +
'#RFXtrx==0.4']
DOMAIN = "rfxtrx"
@@ -37,6 +37,8 @@ def setup(hass, config):
""" Callback all subscribers for RFXtrx gateway. """
# Log RFXCOM event
if not event.device.id_string:
return
entity_id = slugify(event.device.id_string.lower())
packet_id = "".join("{0:02x}".format(x) for x in event.data)
entity_name = "%s : %s" % (entity_id, packet_id)
@@ -0,0 +1,98 @@
"""
homeassistant.components.rollershutter.scsgate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure a SCSGate rollershutter.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/rollershutter.scsgate/
"""
import logging
import homeassistant.components.scsgate as scsgate
from homeassistant.components.rollershutter import RollershutterDevice
DEPENDENCIES = ['scsgate']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Add the SCSGate swiches defined inside of the configuration file. """
devices = config.get('devices')
rollershutters = []
logger = logging.getLogger(__name__)
if devices:
for _, entity_info in devices.items():
if entity_info['scs_id'] in scsgate.SCSGATE.devices:
continue
logger.info("Adding %s scsgate.rollershutter", entity_info['name'])
name = entity_info['name']
scs_id = entity_info['scs_id']
rollershutter = SCSGateRollerShutter(
name=name,
scs_id=scs_id,
logger=logger)
scsgate.SCSGATE.add_device(rollershutter)
rollershutters.append(rollershutter)
add_devices_callback(rollershutters)
# pylint: disable=too-many-arguments, too-many-instance-attributes
class SCSGateRollerShutter(RollershutterDevice):
""" Represents a rollershutter that can be controlled using SCSGate. """
def __init__(self, scs_id, name, logger):
self._scs_id = scs_id
self._name = name
self._logger = logger
@property
def scs_id(self):
""" SCSGate ID """
return self._scs_id
@property
def should_poll(self):
""" No polling needed """
return False
@property
def name(self):
""" The name of the rollershutter. """
return self._name
@property
def current_position(self):
"""
Return current position of rollershutter.
None is unknown, 0 is closed, 100 is fully open.
"""
return None
def move_up(self, **kwargs):
""" Move the rollershutter up. """
from scsgate.tasks import RaiseRollerShutterTask
scsgate.SCSGATE.append_task(
RaiseRollerShutterTask(target=self._scs_id))
def move_down(self, **kwargs):
""" Move the rollershutter down. """
from scsgate.tasks import LowerRollerShutterTask
scsgate.SCSGATE.append_task(
LowerRollerShutterTask(target=self._scs_id))
def stop(self, **kwargs):
""" Stop the device. """
from scsgate.tasks import HaltRollerShutterTask
scsgate.SCSGATE.append_task(HaltRollerShutterTask(target=self._scs_id))
def process_event(self, message):
""" Handle a SCSGate message related with this rollershutter """
self._logger.debug(
"Rollershutter %s, got message %s",
self._scs_id, message.toggled)
+156
View File
@@ -0,0 +1,156 @@
"""
homeassistant.components.scsgate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides support for SCSGate components.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/scsgate/
"""
import logging
from threading import Lock
from homeassistant.core import EVENT_HOMEASSISTANT_STOP
REQUIREMENTS = ['scsgate==0.1.0']
DOMAIN = "scsgate"
SCSGATE = None
_LOGGER = logging.getLogger(__name__)
class SCSGate:
""" Class dealing with the SCSGate device via scsgate.Reactor. """
def __init__(self, device, logger):
self._logger = logger
self._devices = {}
self._devices_to_register = {}
self._devices_to_register_lock = Lock()
self._device_being_registered = None
self._device_being_registered_lock = Lock()
from scsgate.connection import Connection
connection = Connection(device=device, logger=self._logger)
from scsgate.reactor import Reactor
self._reactor = Reactor(
connection=connection,
logger=self._logger,
handle_message=self.handle_message)
def handle_message(self, message):
""" Method called whenever a message is seen on the bus. """
from scsgate.messages import StateMessage, ScenarioTriggeredMessage
self._logger.debug("Received message {}".format(message))
if not isinstance(message, StateMessage) and \
not isinstance(message, ScenarioTriggeredMessage):
msg = "Ignored message {} - not releavant type".format(
message)
self._logger.debug(msg)
return
if message.entity in self._devices:
new_device_activated = False
with self._devices_to_register_lock:
if message.entity == self._device_being_registered:
self._device_being_registered = None
new_device_activated = True
if new_device_activated:
self._activate_next_device()
# pylint: disable=broad-except
try:
self._devices[message.entity].process_event(message)
except Exception as exception:
msg = "Exception while processing event: {}".format(
exception)
self._logger.error(msg)
else:
self._logger.info(
"Ignoring state message for device {} because unknonw".format(
message.entity))
@property
def devices(self):
"""
Dictionary with known devices. Key is device ID, value is the device
itself.
"""
return self._devices
def add_device(self, device):
"""
Adds the specified device to the list of the already registered ones.
Beware: this is not what you usually want to do, take a look at
`add_devices_to_register`
"""
self._devices[device.scs_id] = device
def add_devices_to_register(self, devices):
""" List of devices to be registered. """
with self._devices_to_register_lock:
for device in devices:
self._devices_to_register[device.scs_id] = device
self._activate_next_device()
def _activate_next_device(self):
""" Starts the activation of the first device. """
from scsgate.tasks import GetStatusTask
with self._devices_to_register_lock:
if len(self._devices_to_register) == 0:
return
_, device = self._devices_to_register.popitem()
self._devices[device.scs_id] = device
self._device_being_registered = device.scs_id
self._reactor.append_task(GetStatusTask(target=device.scs_id))
def is_device_registered(self, device_id):
""" Checks whether a device is already registered or not. """
with self._devices_to_register_lock:
if device_id in self._devices_to_register.keys():
return False
with self._device_being_registered_lock:
if device_id == self._device_being_registered:
return False
return True
def start(self):
""" Start the scsgate.Reactor. """
self._reactor.start()
def stop(self):
""" Stop the scsgate.Reactor. """
self._reactor.stop()
def append_task(self, task):
""" Registers a new task to be executed. """
self._reactor.append_task(task)
def setup(hass, config):
""" Setup the SCSGate component. """
device = config['scsgate']['device']
global SCSGATE
# pylint: disable=broad-except
try:
SCSGATE = SCSGate(device=device, logger=_LOGGER)
SCSGATE.start()
except Exception as exception:
_LOGGER.error("Cannot setup SCSGate component: %s", exception)
return False
def stop_monitor(event):
"""
Invoked when home-assistant is exiting. Performs the necessary
cleanups.
"""
_LOGGER.info("Stopping SCSGate monitor thread")
SCSGATE.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_monitor)
return True
@@ -0,0 +1,90 @@
"""
homeassistant.components.sensor.apcupsd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides a sensor to track various status aspects of a UPS.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.apcupsd/
"""
import logging
from homeassistant.const import TEMP_CELCIUS
from homeassistant.helpers.entity import Entity
from homeassistant.components import apcupsd
DEPENDENCIES = [apcupsd.DOMAIN]
DEFAULT_NAME = "UPS Status"
SPECIFIC_UNITS = {
"ITEMP": TEMP_CELCIUS
}
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""
Ensure that the 'type' config value has been set and use a specific unit
of measurement if required.
"""
typ = config.get(apcupsd.CONF_TYPE)
if typ is None:
_LOGGER.error(
"You must include a '%s' when configuring an APCUPSd sensor.",
apcupsd.CONF_TYPE)
return False
typ = typ.upper()
if typ not in apcupsd.DATA.status:
_LOGGER.error(
"Specified '%s' of '%s' does not appear in the APCUPSd status "
"output.", apcupsd.CONF_TYPE, typ)
return False
add_entities((
Sensor(config, apcupsd.DATA, unit=SPECIFIC_UNITS.get(typ)),
))
def infer_unit(value):
"""
If the value ends with any of the units from ALL_UNITS, split the unit
off the end of the value and return the value, unit tuple pair. Else return
the original value and None as the unit.
"""
from apcaccess.status import ALL_UNITS
for unit in ALL_UNITS:
if value.endswith(unit):
return value[:-len(unit)], unit
return value, None
class Sensor(Entity):
""" Generic sensor entity for APCUPSd status values. """
def __init__(self, config, data, unit=None):
self._config = config
self._unit = unit
self._data = data
self._inferred_unit = None
self.update()
@property
def name(self):
""" The name of the UPS sensor. """
return self._config.get("name", DEFAULT_NAME)
@property
def state(self):
""" True if the UPS is online, else False. """
return self._state
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity, if any. """
if self._unit is None:
return self._inferred_unit
return self._unit
def update(self):
""" Get the latest status and use it to update our sensor state. """
key = self._config[apcupsd.CONF_TYPE].upper()
self._state, self._inferred_unit = infer_unit(self._data.status[key])
@@ -39,6 +39,7 @@ OPTION_TYPES = {
'miners_revenue_btc': ['Miners revenue', 'BTC'],
'market_price_usd': ['Market price', 'USD']
}
ICON = 'mdi:currency-btc'
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
@@ -108,6 +109,11 @@ class BitcoinSensor(Entity):
def unit_of_measurement(self):
return self._unit_of_measurement
@property
def icon(self):
""" Icon to use in the frontend, if any. """
return ICON
# pylint: disable=too-many-branches
def update(self):
""" Gets the latest data and updates the states. """
+104
View File
@@ -0,0 +1,104 @@
"""
homeassistant.components.sensor.bloomsky
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support the sensor of a BloomSky weather station.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/sensor.bloomsky/
"""
import logging
import homeassistant.components.bloomsky as bloomsky
from homeassistant.helpers.entity import Entity
DEPENDENCIES = ["bloomsky"]
# These are the available sensors
SENSOR_TYPES = ["Temperature",
"Humidity",
"Rain",
"Pressure",
"Luminance",
"Night",
"UVIndex"]
# Sensor units - these do not currently align with the API documentation
SENSOR_UNITS = {"Temperature": "°F",
"Humidity": "%",
"Pressure": "inHg",
"Luminance": "cd/m²"}
# Which sensors to format numerically
FORMAT_NUMBERS = ["Temperature", "Pressure"]
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Set up the available BloomSky weather sensors. """
logger = logging.getLogger(__name__)
for device_key in bloomsky.BLOOMSKY.devices:
device = bloomsky.BLOOMSKY.devices[device_key]
for variable in config["monitored_conditions"]:
if variable in SENSOR_TYPES:
add_devices([BloomSkySensor(bloomsky.BLOOMSKY,
device,
variable)])
else:
logger.error("Cannot find definition for device: %s", variable)
class BloomSkySensor(Entity):
""" Represents a single sensor in a BloomSky device. """
def __init__(self, bs, device, sensor_name):
self._bloomsky = bs
self._device_id = device["DeviceID"]
self._client_name = device["DeviceName"]
self._sensor_name = sensor_name
self._state = self.process_state(device)
self._sensor_update = ""
@property
def name(self):
""" The name of the BloomSky device and this sensor. """
return "{} {}".format(self._client_name, self._sensor_name)
@property
def state(self):
""" The current state (i.e. value) of this sensor. """
return self._state
@property
def unit_of_measurement(self):
""" This sensor's units. """
return SENSOR_UNITS.get(self._sensor_name, None)
def update(self):
""" Request an update from the BloomSky API. """
self._bloomsky.refresh_devices()
# TS is a Unix epoch timestamp for the last time the BloomSky servers
# heard from this device. If that value hasn't changed, the value has
# not been updated.
last_ts = self._bloomsky.devices[self._device_id]["Data"]["TS"]
if last_ts != self._sensor_update:
self.process_state(self._bloomsky.devices[self._device_id])
self._sensor_update = last_ts
def process_state(self, device):
""" Handle the response from the BloomSky API for this sensor. """
data = device["Data"][self._sensor_name]
if self._sensor_name == "Rain":
if data:
self._state = "Raining"
else:
self._state = "Not raining"
elif self._sensor_name == "Night":
if data:
self._state = "Nighttime"
else:
self._state = "Daytime"
elif self._sensor_name in FORMAT_NUMBERS:
self._state = "{0:.2f}".format(data)
else:
self._state = data
+7 -1
View File
@@ -19,6 +19,7 @@ DEFAULT_NAME = "CPU speed"
ATTR_VENDOR = 'Vendor ID'
ATTR_BRAND = 'Brand'
ATTR_HZ = 'GHz Advertised'
ICON = 'mdi:pulse'
# pylint: disable=unused-variable
@@ -53,7 +54,7 @@ class CpuSpeedSensor(Entity):
return self._unit_of_measurement
@property
def state_attributes(self):
def device_state_attributes(self):
""" Returns the state attributes. """
if self.info is not None:
return {
@@ -62,6 +63,11 @@ class CpuSpeedSensor(Entity):
ATTR_HZ: round(self.info['hz_advertised_raw'][0]/10**9, 2)
}
@property
def icon(self):
""" Icon to use in the frontend, if any. """
return ICON
def update(self):
""" Gets the latest data and updates the state. """
from cpuinfo import cpuinfo
+1 -1
View File
@@ -46,7 +46,7 @@ class DemoSensor(Entity):
return self._unit_of_measurement
@property
def state_attributes(self):
def device_state_attributes(self):
""" Returns the state attributes. """
if self._battery:
return {
+6 -3
View File
@@ -23,6 +23,7 @@ SENSOR_TYPES = {
'temperature': ['Temperature', None],
'humidity': ['Humidity', '%']
}
DEFAULT_NAME = "DHT Sensor"
# Return cached results if last scan was less then this time ago
# DHT11 is able to deliver data once per second, DHT22 once every two
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
@@ -53,12 +54,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
data = DHTClient(Adafruit_DHT, sensor, pin)
dev = []
name = config.get('name', DEFAULT_NAME)
try:
for variable in config['monitored_conditions']:
if variable not in SENSOR_TYPES:
_LOGGER.error('Sensor type: "%s" does not exist', variable)
else:
dev.append(DHTSensor(data, variable, unit))
dev.append(DHTSensor(data, variable, unit, name))
except KeyError:
pass
@@ -69,8 +72,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DHTSensor(Entity):
""" Implements an DHT sensor. """
def __init__(self, dht_client, sensor_type, temp_unit):
self.client_name = 'DHT sensor'
def __init__(self, dht_client, sensor_type, temp_unit, name):
self.client_name = name
self._name = SENSOR_TYPES[sensor_type][0]
self.dht_client = dht_client
self.temp_unit = temp_unit
+13 -26
View File
@@ -1,29 +1,10 @@
"""
homeassistant.components.sensor.ecobee
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sensor platform for Ecobee sensors.
Ecobee Thermostat Component
This component adds support for Ecobee3 Wireless Thermostats.
You will need to setup developer access to your thermostat,
and create and API key on the ecobee website.
The first time you run this component you will see a configuration
component card in Home Assistant. This card will contain a PIN code
that you will need to use to authorize access to your thermostat. You
can do this at https://www.ecobee.com/consumerportal/index.html
Click My Apps, Add application, Enter Pin and click Authorize.
After authorizing the application click the button in the configuration
card. Now your thermostat and sensors should shown in home-assistant.
You can use the optional hold_temp parameter to set whether or not holds
are set indefintely or until the next scheduled event.
ecobee:
api_key: asdfasdfasdfasdfasdfaasdfasdfasdfasdf
hold_temp: True
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.ecobee/
"""
import logging
@@ -32,7 +13,6 @@ from homeassistant.components import ecobee
from homeassistant.const import TEMP_FAHRENHEIT
DEPENDENCIES = ['ecobee']
SENSOR_TYPES = {
'temperature': ['Temperature', TEMP_FAHRENHEIT],
'humidity': ['Humidity', '%'],
@@ -40,12 +20,11 @@ SENSOR_TYPES = {
}
_LOGGER = logging.getLogger(__name__)
ECOBEE_CONFIG_FILE = 'ecobee.conf'
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the sensors. """
""" Sets up the Ecobee sensors. """
if discovery_info is None:
return
data = ecobee.NETWORK
@@ -63,7 +42,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class EcobeeSensor(Entity):
""" An ecobee sensor. """
""" An Ecobee sensor. """
def __init__(self, sensor_name, sensor_type, sensor_index):
self._name = sensor_name + ' ' + SENSOR_TYPES[sensor_type][0]
@@ -76,6 +55,7 @@ class EcobeeSensor(Entity):
@property
def name(self):
""" Returns the name of the Ecobee sensor.. """
return self._name.rstrip()
@property
@@ -83,11 +63,18 @@ class EcobeeSensor(Entity):
""" Returns the state of the device. """
return self._state
@property
def unique_id(self):
"""Unique id of this sensor."""
return "sensor_ecobee_{}_{}".format(self._name, self.index)
@property
def unit_of_measurement(self):
""" Unit of measurement this sensor expresses itself in. """
return self._unit_of_measurement
def update(self):
""" Get the latest state of the sensor. """
data = ecobee.NETWORK
data.update()
for sensor in data.ecobee.get_remote_sensors(self.index):
+99
View File
@@ -0,0 +1,99 @@
"""
homeassistant.components.sensor.mfi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Ubiquiti mFi sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mfi/
"""
import logging
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD,
TEMP_CELCIUS)
from homeassistant.components.sensor import DOMAIN
from homeassistant.helpers.entity import Entity
from homeassistant.helpers import validate_config
REQUIREMENTS = ['mficlient==0.2.2']
_LOGGER = logging.getLogger(__name__)
STATE_ON = 'on'
STATE_OFF = 'off'
DIGITS = {
'volts': 1,
'amps': 1,
'active_power': 0,
'temperature': 1,
}
SENSOR_MODELS = [
'Ubiquiti mFi-THS',
'Ubiquiti mFi-CS',
'Outlet',
'Input Analog',
'Input Digital',
]
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up mFi sensors. """
if not validate_config({DOMAIN: config},
{DOMAIN: ['host',
CONF_USERNAME,
CONF_PASSWORD]},
_LOGGER):
_LOGGER.error('A host, username, and password are required')
return False
host = config.get('host')
port = int(config.get('port', 6443))
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
from mficlient.client import MFiClient
try:
client = MFiClient(host, username, password, port=port)
except client.FailedToLogin as ex:
_LOGGER.error('Unable to connect to mFi: %s', str(ex))
return False
add_devices(MfiSensor(port, hass)
for device in client.get_devices()
for port in device.ports.values()
if port.model in SENSOR_MODELS)
class MfiSensor(Entity):
""" An mFi sensor that exposes tag=value. """
def __init__(self, port, hass):
self._port = port
self._hass = hass
@property
def name(self):
return self._port.label
@property
def state(self):
if self._port.model == 'Input Digital':
return self._port.value > 0 and STATE_ON or STATE_OFF
else:
digits = DIGITS.get(self._port.tag, 0)
return round(self._port.value, digits)
@property
def unit_of_measurement(self):
if self._port.tag == 'temperature':
return TEMP_CELCIUS
elif self._port.tag == 'active_pwr':
return 'Watts'
elif self._port.model == 'Input Digital':
return 'State'
return self._port.tag
def update(self):
self._port.refresh()
+2 -2
View File
@@ -7,7 +7,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mqtt/
"""
import logging
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.const import CONF_VALUE_TEMPLATE, STATE_UNKNOWN
from homeassistant.helpers.entity import Entity
from homeassistant.util import template
import homeassistant.components.mqtt as mqtt
@@ -42,7 +42,7 @@ class MqttSensor(Entity):
""" Represents a sensor that can be updated using MQTT. """
def __init__(self, hass, name, state_topic, qos, unit_of_measurement,
value_template):
self._state = "-"
self._state = STATE_UNKNOWN
self._hass = hass
self._name = name
self._state_topic = state_topic
+20 -21
View File
@@ -1,5 +1,6 @@
"""
homeassistant.components.sensor.mysensors
homeassistant.components.sensor.mysensors.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for MySensors sensors.
@@ -30,7 +31,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for gateway in mysensors.GATEWAYS.values():
# Define the S_TYPES and V_TYPES that the platform should handle as
# states. Map them in a defaultdict(list).
# states. Map them in a dict of lists.
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
@@ -151,48 +152,46 @@ class MySensorsSensor(Entity):
self.gateway.const.SetReq.V_VOLTAGE: 'V',
self.gateway.const.SetReq.V_CURRENT: 'A',
}
unit_map_v15 = {
self.gateway.const.SetReq.V_PERCENTAGE: '%',
}
if float(self.gateway.version) >= 1.5:
if self.gateway.const.SetReq.V_UNIT_PREFIX in self._values:
return self._values[
self.gateway.const.SetReq.V_UNIT_PREFIX]
unit_map.update(unit_map_v15)
unit_map.update({self.gateway.const.SetReq.V_PERCENTAGE: '%'})
return unit_map.get(self.value_type)
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
device_attr = {}
for value_type, value in self._values.items():
if value_type != self.value_type:
device_attr[self.gateway.const.SetReq(value_type).name] = value
return device_attr
@property
def state_attributes(self):
"""Return the state attributes."""
data = {
attr = {
mysensors.ATTR_PORT: self.gateway.port,
mysensors.ATTR_NODE_ID: self.node_id,
mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level,
}
device_attr = self.device_state_attributes
set_req = self.gateway.const.SetReq
if device_attr is not None:
data.update(device_attr)
for value_type, value in self._values.items():
if value_type != self.value_type:
try:
attr[set_req(value_type).name] = value
except ValueError:
_LOGGER.error('value_type %s is not valid for mysensors '
'version %s', value_type,
self.gateway.version)
return attr
return data
@property
def available(self):
"""Return True if entity is available."""
return self.value_type in self._values
def update(self):
"""Update the controller with the latest values from a sensor."""
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.info(
_LOGGER.debug(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == self.gateway.const.SetReq.V_TRIPPED:
self._values[value_type] = STATE_ON if int(
+1 -1
View File
@@ -21,7 +21,7 @@ SENSOR_TYPES = ['humidity',
'last_connection',
'battery_level']
SENSOR_UNITS = {'humidity': '%', 'battery_level': '%'}
SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V'}
SENSOR_TEMP_TYPES = ['temperature',
'target',
+3 -1
View File
@@ -96,5 +96,7 @@ class OneWire(Entity):
equals_pos = lines[1].find('t=')
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp = float(temp_string) / 1000.0
temp = round(float(temp_string) / 1000.0, 1)
if temp < -55 or temp > 125:
return
self._state = temp
+4 -2
View File
@@ -21,7 +21,9 @@ DATA_TYPES = OrderedDict([
('Humidity', '%'),
('Barometer', ''),
('Wind direction', ''),
('Rain rate', '')])
('Rain rate', ''),
('Energy usage', 'W'),
('Total usage', 'W')])
_LOGGER = logging.getLogger(__name__)
@@ -87,7 +89,7 @@ class RfxtrxSensor(Entity):
return self._name
@property
def state_attributes(self):
def device_state_attributes(self):
return self.event.values
@property
@@ -0,0 +1,121 @@
"""
homeassistant.components.sensor.speedtest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Speedtest.net sensor based on speedtest-cli.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.speedtest/
"""
import logging
import sys
import re
from datetime import timedelta
from subprocess import check_output
from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_change
from homeassistant.components.sensor import DOMAIN
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['speedtest-cli==0.3.4']
_LOGGER = logging.getLogger(__name__)
_SPEEDTEST_REGEX = re.compile(r'Ping:\s(\d+\.\d+)\sms[\r\n]+'
r'Download:\s(\d+\.\d+)\sMbit/s[\r\n]+'
r'Upload:\s(\d+\.\d+)\sMbit/s[\r\n]+')
CONF_MONITORED_CONDITIONS = 'monitored_conditions'
CONF_MINUTE = 'minute'
CONF_HOUR = 'hour'
CONF_DAY = 'day'
SENSOR_TYPES = {
'ping': ['Ping', 'ms'],
'download': ['Download', 'Mbit/s'],
'upload': ['Upload', 'Mbit/s'],
}
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup the Speedtest sensor. """
data = SpeedtestData(hass, config)
dev = []
for sensor in config[CONF_MONITORED_CONDITIONS]:
if sensor not in SENSOR_TYPES:
_LOGGER.error('Sensor type: "%s" does not exist', sensor)
else:
dev.append(SpeedtestSensor(data, sensor))
add_devices(dev)
def update(call=None):
""" Update service for manual updates. """
data.update(dt_util.now())
for sensor in dev:
sensor.update()
hass.services.register(DOMAIN, 'update_speedtest', update)
# pylint: disable=too-few-public-methods
class SpeedtestSensor(Entity):
""" Implements a speedtest.net sensor. """
def __init__(self, speedtest_data, sensor_type):
self._name = SENSOR_TYPES[sensor_type][0]
self.speedtest_client = speedtest_data
self.type = sensor_type
self._state = None
self._unit_of_measurement = SENSOR_TYPES[self.type][1]
@property
def name(self):
return '{} {}'.format('Speedtest', self._name)
@property
def state(self):
""" Returns the state of the device. """
return self._state
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity, if any. """
return self._unit_of_measurement
def update(self):
""" Gets the latest data from Forecast.io and updates the states. """
data = self.speedtest_client.data
if data is not None:
if self.type == 'ping':
self._state = data['ping']
elif self.type == 'download':
self._state = data['download']
elif self.type == 'upload':
self._state = data['upload']
class SpeedtestData(object):
""" Gets the latest data from speedtest.net. """
def __init__(self, hass, config):
self.data = None
self.hass = hass
self.path = hass.config.path
track_time_change(self.hass, self.update,
minute=config.get(CONF_MINUTE, 0),
hour=config.get(CONF_HOUR, None),
day=config.get(CONF_DAY, None))
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self, now):
""" Gets the latest data from speedtest.net. """
_LOGGER.info('Executing speedtest')
re_output = _SPEEDTEST_REGEX.split(
check_output([sys.executable, self.path(
'lib', 'speedtest_cli.py'), '--simple']).decode("utf-8"))
self.data = {'ping': round(float(re_output[1]), 2),
'download': round(float(re_output[2]), 2),
'upload': round(float(re_output[3]), 2)}
@@ -23,6 +23,7 @@ ATTR_DEPARTURE_TIME2 = 'Next on departure'
ATTR_START = 'Start'
ATTR_TARGET = 'Destination'
ATTR_REMAINING_TIME = 'Remaining time'
ICON = 'mdi:bus'
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
@@ -74,7 +75,7 @@ class SwissPublicTransportSensor(Entity):
return self._state
@property
def state_attributes(self):
def device_state_attributes(self):
""" Returns the state attributes. """
if self._times is not None:
return {
@@ -86,6 +87,11 @@ class SwissPublicTransportSensor(Entity):
':'.join(str(self._times[2]).split(':')[:2]))
}
@property
def icon(self):
""" Icon to use in the frontend, if any. """
return ICON
# pylint: disable=too-many-branches
def update(self):
""" Gets the latest data from opendata.ch and updates the states. """
@@ -67,10 +67,12 @@ class SystemMonitorSensor(Entity):
@property
def name(self):
""" Returns the name of the sensor. """
return self._name.rstrip()
@property
def icon(self):
""" Icon to use in the frontend, if any. """
return SENSOR_TYPES[self.type][2]
@property
@@ -80,10 +82,12 @@ class SystemMonitorSensor(Entity):
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity, if any. """
return self._unit_of_measurement
# pylint: disable=too-many-branches
def update(self):
""" Get the latest system informations. """
import psutil
if self.type == 'disk_use_percent':
self._state = psutil.disk_usage(self.argument).percent
+63 -40
View File
@@ -11,7 +11,9 @@ import logging
from datetime import datetime
from homeassistant.const import TEMP_CELCIUS, ATTR_BATTERY_LEVEL
from homeassistant.const import (TEMP_CELCIUS,
ATTR_BATTERY_LEVEL,
DEVICE_DEFAULT_NAME)
from homeassistant.helpers.entity import Entity
from homeassistant.components import tellduslive
@@ -44,50 +46,86 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up Tellstick sensors. """
if discovery_info is None:
return
sensors = tellduslive.NETWORK.get_sensors()
devices = []
for component in sensors:
for sensor in component["data"]:
# one component can have more than one sensor
# (e.g. both humidity and temperature)
devices.append(TelldusLiveSensor(component["id"],
component["name"],
sensor["name"]))
add_devices(devices)
add_devices(TelldusLiveSensor(sensor) for sensor in discovery_info)
class TelldusLiveSensor(Entity):
""" Represents a Telldus Live sensor. """
def __init__(self, sensor_id, sensor_name, sensor_type):
self._sensor_id = sensor_id
self._sensor_type = sensor_type
self._state = None
self._name = sensor_name + ' ' + SENSOR_TYPES[sensor_type][0]
self._last_update = None
self._battery_level = None
def __init__(self, sensor_id):
self._id = sensor_id
self.update()
_LOGGER.debug("created sensor %s", self)
def update(self):
""" update sensor values """
tellduslive.NETWORK.update_sensors()
self._sensor = tellduslive.NETWORK.get_sensor(self._id)
@property
def _sensor_name(self):
return self._sensor["name"]
@property
def _sensor_value(self):
return self._sensor["data"]["value"]
@property
def _sensor_type(self):
return self._sensor["data"]["name"]
@property
def _battery_level(self):
sensor_battery_level = self._sensor.get("battery")
return round(sensor_battery_level * 100 / 255) \
if sensor_battery_level else None
@property
def _last_updated(self):
sensor_last_updated = self._sensor.get("lastUpdated")
return str(datetime.fromtimestamp(sensor_last_updated)) \
if sensor_last_updated else None
@property
def _value_as_temperature(self):
return round(float(self._sensor_value), 1)
@property
def _value_as_humidity(self):
return int(round(float(self._sensor_value)))
@property
def name(self):
""" Returns the name of the device. """
return self._name
return "{} {}".format(self._sensor_name or DEVICE_DEFAULT_NAME,
self.quantity_name)
@property
def available(self):
return not self._sensor.get("offline", False)
@property
def state(self):
""" Returns the state of the device. """
return self._state
if self._sensor_type == SENSOR_TYPE_TEMP:
return self._value_as_temperature
elif self._sensor_type == SENSOR_TYPE_HUMIDITY:
return self._value_as_humidity
@property
def state_attributes(self):
attrs = dict()
def device_state_attributes(self):
attrs = {}
if self._battery_level is not None:
attrs[ATTR_BATTERY_LEVEL] = self._battery_level
if self._last_update is not None:
attrs[ATTR_LAST_UPDATED] = self._last_update
if self._last_updated is not None:
attrs[ATTR_LAST_UPDATED] = self._last_updated
return attrs
@property
def quantity_name(self):
""" name of quantity """
return SENSOR_TYPES[self._sensor_type][0]
@property
def unit_of_measurement(self):
return SENSOR_TYPES[self._sensor_type][1]
@@ -95,18 +133,3 @@ class TelldusLiveSensor(Entity):
@property
def icon(self):
return SENSOR_TYPES[self._sensor_type][2]
def update(self):
values = tellduslive.NETWORK.get_sensor_value(self._sensor_id,
self._sensor_type)
self._state, self._battery_level, self._last_update = values
self._state = float(self._state)
if self._sensor_type == SENSOR_TYPE_TEMP:
self._state = round(self._state, 1)
elif self._sensor_type == SENSOR_TYPE_HUMIDITY:
self._state = int(round(self._state))
self._battery_level = round(self._battery_level * 100 / 255) # percent
self._last_update = str(datetime.fromtimestamp(self._last_update))
+27 -7
View File
@@ -9,16 +9,20 @@ https://home-assistant.io/components/sensor.template/
"""
import logging
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity import Entity, generate_entity_id
from homeassistant.core import EVENT_STATE_CHANGED
from homeassistant.const import (
ATTR_FRIENDLY_NAME,
CONF_VALUE_TEMPLATE,
ATTR_UNIT_OF_MEASUREMENT)
from homeassistant.util import template
from homeassistant.util import template, slugify
from homeassistant.exceptions import TemplateError
from homeassistant.components.sensor import DOMAIN
ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__)
CONF_SENSORS = 'sensors'
STATE_ERROR = 'error'
@@ -34,9 +38,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
for device, device_config in config[CONF_SENSORS].items():
if device != slugify(device):
_LOGGER.error("Found invalid key for sensor.template: %s. "
"Use %s instead", device, slugify(device))
continue
if not isinstance(device_config, dict):
_LOGGER.error("Missing configuration data for sensor %s", device)
continue
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
unit_of_measurement = device_config.get(ATTR_UNIT_OF_MEASUREMENT)
state_template = device_config.get(CONF_VALUE_TEMPLATE)
@@ -44,14 +55,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error(
"Missing %s for sensor %s", CONF_VALUE_TEMPLATE, device)
continue
sensors.append(
SensorTemplate(
hass,
device,
friendly_name,
unit_of_measurement,
state_template)
)
if sensors is None:
if not sensors:
_LOGGER.error("No sensors added")
return False
add_devices(sensors)
@@ -64,10 +77,15 @@ class SensorTemplate(Entity):
# pylint: disable=too-many-arguments
def __init__(self,
hass,
device_id,
friendly_name,
unit_of_measurement,
state_template):
self.entity_id = generate_entity_id(
ENTITY_ID_FORMAT, device_id,
hass=hass)
self.hass = hass
self._name = friendly_name
self._unit_of_measurement = unit_of_measurement
@@ -76,10 +94,7 @@ class SensorTemplate(Entity):
def _update_callback(_event):
""" Called when the target device changes state. """
# This can be called before the entity is properly
# initialised, so check before updating state,
if self.entity_id:
self.update_ha_state(True)
self.update_ha_state(True)
self.hass.bus.listen(EVENT_STATE_CHANGED, _update_callback)
@@ -108,4 +123,9 @@ class SensorTemplate(Entity):
self._state = template.render(self.hass, self._template)
except TemplateError as ex:
self._state = STATE_ERROR
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
# Common during HA startup - so just a warning
_LOGGER.warning(ex)
return
_LOGGER.error(ex)
@@ -59,6 +59,15 @@ class TimeDateSensor(Entity):
""" Returns the state of the device. """
return self._state
@property
def icon(self):
if "date" in self.type and "time" in self.type:
return "mdi:calendar-clock"
elif "date" in self.type:
return "mdi:calendar"
else:
return "mdi:clock"
def update(self):
""" Gets the latest data and updates the states. """
+2 -1
View File
@@ -67,7 +67,7 @@ class TwitchSensor(Entity):
self._state = STATE_OFFLINE
@property
def state_attributes(self):
def device_state_attributes(self):
""" Returns the state attributes. """
if self._state == STATE_STREAMING:
return {
@@ -78,4 +78,5 @@ class TwitchSensor(Entity):
@property
def icon(self):
""" Icon to use in the frontend, if any. """
return ICON
+2 -2
View File
@@ -15,7 +15,7 @@ from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME,
TEMP_CELCIUS, TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_STOP)
REQUIREMENTS = ['pyvera==0.2.7']
REQUIREMENTS = ['pyvera==0.2.8']
_LOGGER = logging.getLogger(__name__)
@@ -118,7 +118,7 @@ class VeraSensor(Entity):
return '%'
@property
def state_attributes(self):
def device_state_attributes(self):
attr = {}
if self.vera_device.has_battery:
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
+1 -1
View File
@@ -11,7 +11,7 @@ import logging
from homeassistant.helpers.entity import Entity
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
REQUIREMENTS = ['python-wink==0.4.2']
REQUIREMENTS = ['python-wink==0.5.0']
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -14,6 +14,7 @@ from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "Worldclock Sensor"
ICON = 'mdi:clock'
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -54,6 +55,11 @@ class WorldClockSensor(Entity):
""" Returns the state of the device. """
return self._state
@property
def icon(self):
""" Icon to use in the frontend, if any. """
return ICON
def update(self):
""" Gets the time and updates the states. """
self._state = dt_util.datetime_to_time_str(
+2 -2
View File
@@ -28,7 +28,7 @@ SENSOR_TYPES = {
'temperature': ['Temperature', '°C'],
'windSpeed': ['Wind speed', 'm/s'],
'windGust': ['Wind gust', 'm/s'],
'pressure': ['Pressure', 'mbar'],
'pressure': ['Pressure', 'hPa'],
'windDirection': ['Wind direction', '°'],
'humidity': ['Humidity', '%'],
'fog': ['Fog', '%'],
@@ -100,7 +100,7 @@ class YrSensor(Entity):
return self._state
@property
def state_attributes(self):
def device_state_attributes(self):
""" Returns state attributes. """
data = {
'about': "Weather forecast from yr.no, delivered by the"
+6 -5
View File
@@ -1,9 +1,12 @@
"""
homeassistant.components.sensor.zigbee
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Contains functionality to use a ZigBee device as a sensor.
"""
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.zigbee/
"""
import logging
from binascii import hexlify
@@ -36,9 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class ZigBeeTemperatureSensor(Entity):
"""
Allows usage of an XBee Pro as a temperature sensor.
"""
""" Allows usage of an XBee Pro as a temperature sensor. """
def __init__(self, hass, config):
self._config = config
self._temp = None
+14 -2
View File
@@ -22,14 +22,24 @@ from homeassistant.components.zwave import (
from homeassistant.const import (
STATE_ON, STATE_OFF, TEMP_CELCIUS, TEMP_FAHRENHEIT)
PHILIO = '013c'
PHILIO_SLIM_SENSOR = '0002'
PHILIO = '0x013c'
PHILIO_SLIM_SENSOR = '0x0002'
PHILIO_SLIM_SENSOR_MOTION = (PHILIO, PHILIO_SLIM_SENSOR, 0)
FIBARO = '0x010f'
FIBARO_WALL_PLUG = '0x1000'
FIBARO_WALL_PLUG_SENSOR_METER = (FIBARO, FIBARO_WALL_PLUG, 8)
WORKAROUND_NO_OFF_EVENT = 'trigger_no_off_event'
WORKAROUND_IGNORE = 'ignore'
DEVICE_MAPPINGS = {
PHILIO_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT,
# For some reason Fibaro Wall Plug reports 2 power consumptions.
# One value updates as the power consumption changes
# and the other does not change.
FIBARO_WALL_PLUG_SENSOR_METER: WORKAROUND_IGNORE,
}
@@ -66,6 +76,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([
ZWaveTriggerSensor(value, hass, re_arm_multiplier * 8)
])
elif DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_IGNORE:
return
# generic Device mappings
elif value.command_class == COMMAND_CLASS_SENSOR_BINARY:
+90
View File
@@ -0,0 +1,90 @@
"""
homeassistant.components.splunk
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Splunk component which allows you to send data to an Splunk instance
utilizing the HTTP Event Collector.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/splunk/
"""
import json
import logging
import requests
import homeassistant.util as util
from homeassistant.helpers import validate_config
from homeassistant.helpers import state as state_helper
from homeassistant.const import EVENT_STATE_CHANGED
_LOGGER = logging.getLogger(__name__)
DOMAIN = "splunk"
DEPENDENCIES = []
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = '8088'
DEFAULT_SSL = False
CONF_HOST = 'host'
CONF_PORT = 'port'
CONF_TOKEN = 'token'
CONF_SSL = 'SSL'
def setup(hass, config):
""" Setup the Splunk component. """
if not validate_config(config, {DOMAIN: ['token']}, _LOGGER):
_LOGGER.error("You must include the token for your HTTP "
"Event Collector input in Splunk.")
return False
conf = config[DOMAIN]
host = conf[CONF_HOST]
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
token = util.convert(conf.get(CONF_TOKEN), str)
use_ssl = util.convert(conf.get(CONF_SSL), bool, DEFAULT_SSL)
if use_ssl:
uri_scheme = "https://"
else:
uri_scheme = "http://"
event_collector = uri_scheme + host + ":" + str(port) + \
"/services/collector/event"
headers = {'Authorization': 'Splunk ' + token}
def splunk_event_listener(event):
""" Listen for new messages on the bus and sends them to Splunk. """
state = event.data.get('new_state')
if state is None:
return
try:
_state = state_helper.state_as_number(state)
except ValueError:
_state = state.state
json_body = [
{
'domain': state.domain,
'entity_id': state.object_id,
'attributes': state.attributes,
'time': str(event.time_fired),
'value': _state,
}
]
try:
payload = {"host": event_collector,
"event": json_body}
requests.post(event_collector, data=json.dumps(payload),
headers=headers)
except requests.exceptions.RequestException as error:
_LOGGER.exception('Error saving event to Splunk: %s', error)
hass.bus.listen(EVENT_STATE_CHANGED, splunk_event_listener)
return True
+6 -17
View File
@@ -8,10 +8,8 @@ https://home-assistant.io/components/statsd/
"""
import logging
import homeassistant.util as util
from homeassistant.const import (EVENT_STATE_CHANGED, STATE_ON, STATE_OFF,
STATE_UNLOCKED, STATE_LOCKED, STATE_UNKNOWN)
from homeassistant.components.sun import (STATE_ABOVE_HORIZON,
STATE_BELOW_HORIZON)
from homeassistant.const import EVENT_STATE_CHANGED
from homeassistant.helpers import state as state_helper
_LOGGER = logging.getLogger(__name__)
@@ -61,19 +59,10 @@ def setup(hass, config):
if state is None:
return
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON):
_state = 1
elif state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
STATE_BELOW_HORIZON):
_state = 0
else:
_state = state.state
if _state == '':
return
try:
_state = float(_state)
except ValueError:
pass
try:
_state = state_helper.state_as_number(state)
except ValueError:
return
if not isinstance(_state, NUM_TYPES):
return
@@ -129,11 +129,6 @@ class SwitchDevice(ToggleEntity):
""" Is the device in standby. """
return None
@property
def device_state_attributes(self):
""" Returns device specific state attributes. """
return None
@property
def state_attributes(self):
""" Returns optional state attributes. """
@@ -144,9 +139,4 @@ class SwitchDevice(ToggleEntity):
if value:
data[attr] = value
device_attr = self.device_state_attributes
if device_attr is not None:
data.update(device_attr)
return data
+103
View File
@@ -0,0 +1,103 @@
"""
homeassistant.components.switch.mfi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Ubiquiti mFi switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.mfi/
"""
import logging
from homeassistant.components.switch import DOMAIN, SwitchDevice
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers import validate_config
REQUIREMENTS = ['mficlient==0.2.2']
_LOGGER = logging.getLogger(__name__)
SWITCH_MODELS = [
'Outlet',
'Output 5v',
'Output 12v',
'Output 24v',
]
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up mFi sensors. """
if not validate_config({DOMAIN: config},
{DOMAIN: ['host',
CONF_USERNAME,
CONF_PASSWORD]},
_LOGGER):
_LOGGER.error('A host, username, and password are required')
return False
host = config.get('host')
port = int(config.get('port', 6443))
username = config.get('username')
password = config.get('password')
from mficlient.client import MFiClient
try:
client = MFiClient(host, username, password, port=port)
except client.FailedToLogin as ex:
_LOGGER.error('Unable to connect to mFi: %s', str(ex))
return False
add_devices(MfiSwitch(port)
for device in client.get_devices()
for port in device.ports.values()
if port.model in SWITCH_MODELS)
class MfiSwitch(SwitchDevice):
""" An mFi switch-able device. """
def __init__(self, port):
self._port = port
self._target_state = None
@property
def should_poll(self):
return True
@property
def unique_id(self):
return self._port.ident
@property
def name(self):
return self._port.label
@property
def is_on(self):
return self._port.output
def update(self):
self._port.refresh()
if self._target_state is not None:
self._port.data['output'] = float(self._target_state)
self._target_state = None
def turn_on(self):
self._port.control(True)
self._target_state = True
def turn_off(self):
self._port.control(False)
self._target_state = False
@property
def current_power_mwh(self):
return int(self._port.data.get('active_pwr', 0) * 1000)
@property
def device_state_attributes(self):
attr = {}
attr['volts'] = round(self._port.data.get('v_rms', 0), 1)
attr['amps'] = round(self._port.data.get('i_rms', 0), 1)
return attr
+28 -25
View File
@@ -1,5 +1,6 @@
"""
homeassistant.components.switch.mysensors
homeassistant.components.switch.mysensors.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for MySensors switches.
@@ -29,14 +30,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for gateway in mysensors.GATEWAYS.values():
# Define the S_TYPES and V_TYPES that the platform should handle as
# states. Map them in a defaultdict(list).
# states. Map them in a dict of lists.
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_DOOR: [set_req.V_ARMED],
pres.S_MOTION: [set_req.V_ARMED],
pres.S_SMOKE: [set_req.V_ARMED],
pres.S_LIGHT: [set_req.V_LIGHT],
pres.S_LOCK: [set_req.V_LOCK_STATUS],
}
if float(gateway.version) >= 1.5:
@@ -48,7 +48,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
pres.S_VIBRATION: [set_req.V_ARMED],
pres.S_MOISTURE: [set_req.V_ARMED],
})
map_sv_types[pres.S_LIGHT].append(set_req.V_STATUS)
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
@@ -100,28 +99,24 @@ class MySensorsSwitch(SwitchDevice):
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
device_attr = {}
for value_type, value in self._values.items():
if value_type != self.value_type:
device_attr[self.gateway.const.SetReq(value_type).name] = value
return device_attr
@property
def state_attributes(self):
"""Return the state attributes."""
data = {
attr = {
mysensors.ATTR_PORT: self.gateway.port,
mysensors.ATTR_NODE_ID: self.node_id,
mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level,
}
device_attr = self.device_state_attributes
set_req = self.gateway.const.SetReq
if device_attr is not None:
data.update(device_attr)
return data
for value_type, value in self._values.items():
if value_type != self.value_type:
try:
attr[set_req(value_type).name] = value
except ValueError:
_LOGGER.error('value_type %s is not valid for mysensors '
'version %s', value_type,
self.gateway.version)
return attr
@property
def is_on(self):
@@ -134,25 +129,33 @@ class MySensorsSwitch(SwitchDevice):
"""Turn the switch on."""
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type, 1)
self._values[self.value_type] = STATE_ON
self.update_ha_state()
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[self.value_type] = STATE_ON
self.update_ha_state()
def turn_off(self):
"""Turn the switch off."""
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type, 0)
self._values[self.value_type] = STATE_OFF
self.update_ha_state()
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[self.value_type] = STATE_OFF
self.update_ha_state()
@property
def available(self):
"""Return True if entity is available."""
return self.value_type in self._values
def update(self):
"""Update the controller with the latest value from a sensor."""
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.info(
_LOGGER.debug(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == self.gateway.const.SetReq.V_ARMED or \
value_type == self.gateway.const.SetReq.V_STATUS or \
value_type == self.gateway.const.SetReq.V_LIGHT or \
value_type == self.gateway.const.SetReq.V_LOCK_STATUS:
self._values[value_type] = (

Some files were not shown because too many files have changed in this diff Show More