Compare commits

..

994 Commits
0.10 ... 0.13.1

Author SHA1 Message Date
Paulus Schoutsen
29b6782b42 Merge pull request #1229 from balloob/hotfix/state-as-number
0.13.1 / Fix: state_as_number always return float
2016-02-13 08:23:14 -08:00
Paulus Schoutsen
d2df485bea Version bump to 0.13.1 2016-02-13 08:17:47 -08:00
Paulus Schoutsen
77b141a355 Update Wink to 0.6 2016-02-13 08:17:38 -08:00
Paulus Schoutsen
1571b33e4a Fix: state_as_number always return float 2016-02-13 00:08:32 -08:00
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
Paulus Schoutsen
d0bcec12b9 Merge pull request #1001 from balloob/dev
0.12
2016-01-29 22:37:38 -08:00
Paulus Schoutsen
9fc62c1851 Version bump to 0.12 2016-01-29 22:33:46 -08:00
Paulus Schoutsen
23d88cd4ad Merge pull request #1047 from haraldnagel/dev
Fix #1046 by correcting platform name
2016-01-29 22:14:21 -08:00
Harald Nagel
86f433067c Fix #1046 by correcting platform name 2016-01-30 06:05:09 +00: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
Paulus Schoutsen
75232c43ce Merge pull request #1044 from w1ll1am23/googlevoice
Google Voice SMS notification support
2016-01-29 19:32:23 -08: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
William Scanlon
01a743c7d4 Log if target == NULL 2016-01-29 22:09:59 -05:00
William Scanlon
28f4283b40 Validate target 2016-01-29 22:04:02 -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
William Scanlon
375faa9c91 Fixed style error 2016-01-29 21:53:06 -05:00
Paulus Schoutsen
ef132e4583 Add tests for color util 2016-01-29 18:44:21 -08:00
Paulus Schoutsen
05cbe54db3 Remove owntracks from coverage 2016-01-29 18:02:42 -08:00
Paulus Schoutsen
ae7697b900 Merge pull request #1037 from balloob/owntracks_mobile_beacons
Owntracks - mobile ibeacon tracking.
2016-01-29 18:01:31 -08:00
Martin Hjelmare
a8f7bc2324 Merge pull request #1034 from MartinHjelmare/refactor-s_v_types
Mysensors: Refactor s_types and v_types mapping
2016-01-30 01:04:06 +01:00
MartinHjelmare
9db0987e53 Refactor s_types and v_types mapping
To be able to handle new platforms with multiple used v_types but
single states, a new more strict mapping was required. Each s_type is
now mapped to its corresponding v_type(s) in a dict of lists.
2016-01-30 00:55:13 +01:00
pavoni
515307b404 Remove dev/null from build script. 2016-01-29 22:11:01 +00:00
pavoni
03e7ac2a0e Fix typo. 2016-01-29 22:11:00 +00:00
pavoni
c5cdf6d7cf Copy kwargs, remove traces. 2016-01-29 22:11:00 +00:00
pavoni
a3abd8bb08 Revise tracked beacon name. 2016-01-29 22:11:00 +00:00
pavoni
5a7e380396 Revise beacon name. Add debug. 2016-01-29 22:11:00 +00:00
pavoni
09ef2e1b8c Add docstring, remove trace. 2016-01-29 22:11:00 +00:00
pavoni
80d2f35cc5 First cut mobile beacon tracking. 2016-01-29 22:11:00 +00:00
William Scanlon
ad57f27989 Added test exclude in .coveragerc 2016-01-29 16:54:56 -05:00
William Scanlon
a6720f54b3 Fixed style errors 2016-01-29 16:48:01 -05:00
William Scanlon
aaf75c7e45 Added target support for googlevoice 2016-01-29 14:14:27 -05:00
Martin Hjelmare
c5de42e7b5 Merge pull request #1033 from MartinHjelmare/refactor-unit-of-measurement
Mysensors: Refactor unit of measurement
2016-01-29 18:21:15 +01:00
Paulus Schoutsen
d6bb6a0777 Merge pull request #1020 from kennedyshead/dev
Adding service for logbook
2016-01-29 09:19:34 -08:00
Paulus Schoutsen
f87c7d6732 Merge pull request #1023 from flyte/zigbee-pr
Add ZigBee integration and components.
2016-01-29 09:18:10 -08:00
MartinHjelmare
575e97a051 Refactor unit of measurement
* Add dict with map of units to v_types.
* Avoid numerous if, elif branches and return statements.
* Add custom unit decoupled from other units.
2016-01-29 18:11:58 +01:00
Flyte
902077d78b Catch ZigBee exceptions when communicating with the devices and log appropriate messages. 2016-01-29 16:45:49 +00:00
Flyte
c17a4fca80 ZigBee: Ensure correct entity types are used for each component. 2016-01-29 12:00:53 +00:00
Flyte
241a768983 ZigBee: Remove extraneous update_ha_state() usage and use it more appropriately for initial state update. 2016-01-29 11:33:15 +00:00
Flyte
72cca0a91a ZigBee: Handle case in which Serial port is unable to open 2016-01-29 10:48:15 +00:00
Greg Dowling
dd7a7f4c75 Merge pull request #1036 from balloob/owntracks_beacon2
Improve owntracks events, add support for stationary ibeacons.
2016-01-29 09:47:20 +00:00
pavoni
468a8a1013 Improve owntracks events, add support for stationary ibeacons. 2016-01-29 09:39:00 +00:00
magnusknutas
24d84dbb42 Added entity_id and domain 2016-01-29 08:13:46 +01:00
Paulus Schoutsen
893a14e8db Merge pull request #1035 from balloob/fix-imports
Fix imports and rename insteon to insteon_hub
2016-01-28 21:53:21 -08:00
Paulus Schoutsen
cbc6323438 Fix imports 2016-01-28 21:45:26 -08:00
Paulus Schoutsen
d6db00b55a Merge pull request #1022 from haraldnagel/dev
Move Insteon API KEY into configuration.yaml
2016-01-28 21:36:35 -08:00
Harald Nagel
eebb736bf8 Add ability to control dimmable sources 2016-01-29 04:36:40 +00:00
Harald Nagel
f192a15a8f Remove unnecessary try around import 2016-01-29 02:35:04 +00:00
William Scanlon
9ec44fbe32 Updated googlevoicesms version 2016-01-28 21:19:15 -05:00
William Scanlon
ffc06e8bcb pygooglevoice-sms support 2016-01-28 21:07:59 -05:00
William Scanlon
9c7b2ce9fd pygooglevoice-sms support 2016-01-28 21:07:16 -05:00
Paulus Schoutsen
1de7dcdb5f Merge pull request #1025 from molobrakos/yr-position
allow monitoring weather at other location than home
2016-01-28 08:49:48 -08:00
Erik
9e7886b909 allow monitoring weather at other location than home (such as summer house) 2016-01-28 17:16:02 +01:00
Flyte
4045fb6862 Fix import-before-install of xbee-helper dependency. 2016-01-28 13:44:37 +00:00
Flyte
dda4f84150 Add zigbee components. 2016-01-28 13:18:29 +00:00
magnusknutas
fda88f8fda logbook.log the name of the game 2016-01-28 09:51:05 +01:00
Harald Nagel
3c05c8d1db Use validate_config, watch for import exception 2016-01-28 05:55:36 +00:00
Harald Nagel
21ec435430 Move Insteon API KEY into configuration.yaml 2016-01-28 03:01:32 +00:00
magnusknutas
24893bc28d Less complex service setup 2016-01-27 18:03:37 +01:00
magnusknutas
403b9cbe3e And with template support 2016-01-27 17:37:15 +01:00
magnusknutas
8b0d19835c Cleanup 2016-01-27 17:32:57 +01:00
magnusknutas
c31522eea9 Logbook service 2016-01-27 17:27:55 +01:00
Paulus Schoutsen
ee33aa73e1 Merge pull request #1017 from fabaff/psutil-upgrade
Upgrade psutil to 3.4.2
2016-01-27 08:08:21 -08:00
Paulus Schoutsen
47498e4aa9 Merge pull request #1016 from fabaff/cpuinfo-upgrade
Upgrade py-cpuinfo to 0.1.8
2016-01-27 08:07:55 -08:00
Fabian Affolter
ef4661f1e6 Upgrade psutil to 3.4.2 2016-01-27 12:26:38 +01:00
Fabian Affolter
b4070cfb78 Upgrade py-cpuinfo to 0.1.8 2016-01-27 11:43:49 +01:00
Fabian Affolter
c750f16275 Update docstings 2016-01-27 09:34:14 +01:00
Fabian Affolter
2248c271fa Add link to docs 2016-01-27 09:25:44 +01:00
Fabian Affolter
87b33d5098 Move configuration details to docs 2016-01-27 09:23:44 +01:00
Fabian Affolter
cf7c06d307 Update docstring and fix typos 2016-01-27 09:21:21 +01:00
Fabian Affolter
70d95cb6aa Add link to docs 2016-01-27 09:08:04 +01:00
Fabian Affolter
2c151c6db9 Update docstring 2016-01-27 09:01:08 +01:00
Fabian Affolter
3fc5a60634 Add link to docs 2016-01-27 09:00:01 +01:00
Fabian Affolter
94d2f23cfc Add link to docs and remove configuration details 2016-01-27 08:08:04 +01:00
Fabian Affolter
4bd102ddf5 Add twitter docs 2016-01-27 08:08:04 +01:00
Fabian Affolter
232aa792f1 Add link to docs and update docstrings 2016-01-27 08:08:04 +01:00
Paulus Schoutsen
ce9f76a0be Add onewire to coverage 2016-01-26 23:07:01 -08:00
Paulus Schoutsen
f3f592cdec Merge pull request #982 from stefan-jonasson/dev
Updated the entity names for all z-wave devices.
2016-01-26 22:51:34 -08:00
Stefan Jonasson
1d2cd0811b Updated Imports.
ZWave modules now import the domain from the base domain.
2016-01-27 07:37:41 +01:00
Paulus Schoutsen
886b581d2a Merge pull request #1002 from kennedyshead/dev
Running periodic tasks
2016-01-26 21:42:06 -08:00
Paulus Schoutsen
88387d3123 Merge branch 'pr/78' into dev
Conflicts:
	homeassistant/const.py
2016-01-26 21:07:53 -08: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
Martin Hjelmare
14cd27aaa7 Merge pull request #1011 from MartinHjelmare/fix-unit-of-measurement
Fix unit of measurement
2016-01-27 03:40:50 +01:00
MartinHjelmare
2e175b88bc Fix unit of measurement
* Add version check in unit_of_measurement, to avoid error.
* Remove not needed metric check in unit_of_measurement.
* Add more value types to unit_of_measurement.
* Fix unit prefix
* Remove unused SCAN_INTERVAL variable.
2016-01-27 03:18:59 +01:00
Paulus Schoutsen
537a7789fd Merge pull request #1005 from fabaff/astral-upgrade
Updgrade to astral 0.9
2016-01-26 17:48:14 -08:00
Paulus Schoutsen
80d1ab78dd Merge pull request #1006 from fabaff/influxdb-upgrade
Upgrade influxdb to 2.11.0
2016-01-26 17:47:59 -08:00
Paulus Schoutsen
821238f889 Merge pull request #1007 from fabaff/telegram-upgrade
Upgrade python-telegram-bot to 3.2.0
2016-01-26 17:47:42 -08:00
Paulus Schoutsen
33a9ec0106 Merge pull request #1008 from fabaff/blockchain-upgrade
Upgrade blockchain to 1.2.1
2016-01-26 17:47:14 -08:00
Paulus Schoutsen
a8f0f313c8 Merge pull request #1009 from fabaff/docstrings
Update docstrings
2016-01-26 17:46:56 -08:00
Martin Hjelmare
6796219f37 Merge pull request #1010 from MartinHjelmare/fix-device-attr
Fix device_state_attributes
2016-01-27 02:43:34 +01:00
MartinHjelmare
1dae22a465 Fix device_state_attributes
* Return a dict with keys as the names of the enum members instead of
	the member values.
* This fixes a TypeError: unorderable types: int() < str()
2016-01-27 02:33:21 +01:00
Fabian Affolter
7aba78f96e Update docstrings 2016-01-27 00:08:06 +01:00
Fabian Affolter
06cf07b097 Upgrade blockchain to 1.2.1 2016-01-26 23:53:30 +01:00
Fabian Affolter
271af2c608 Upgrade python-telegram-bot to 3.2.0 2016-01-26 23:47:06 +01:00
Fabian Affolter
8c6ce217e6 Upgrade influxdb to 2.11.0 2016-01-26 23:45:39 +01:00
Fabian Affolter
5410a0c8f6 Updgrade to astral 0.9 2016-01-26 23:34:25 +01:00
Stefan Jonasson
1a422ecd5a Moved common z-wave node functionality to a common z-wave class 2016-01-26 22:11:27 +01:00
magnusknutas
6add5e387b Remove unused import 2016-01-26 21:41:12 +01:00
magnusknutas
ca070a36e3 Dont wrap '/int' parameters with tuple 2016-01-26 21:38:07 +01:00
magnusknutas
2c3a6e7905 Remove not needed log warning 2016-01-26 21:30:00 +01:00
magnusknutas
0a4e857901 And a test just to see if nothing works if not providing any vars 2016-01-26 21:21:54 +01:00
magnusknutas
d4b444823c Tests for time and a fix for wrong access of _LOGGER 2016-01-26 21:11:30 +01:00
magnusknutas
2382dffbf4 pushed test code :) fixed now 2016-01-26 20:49:51 +01:00
magnusknutas
33b0f4d05d Fixes bug in time trigger and adds test for ex. /two 2016-01-26 20:43:29 +01:00
Stefan Jonasson
5b6371ecda Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	homeassistant/components/light/zwave.py
#	homeassistant/components/switch/zwave.py
2016-01-26 20:31:42 +01:00
magnusknutas
6f1f8ffea0 Merge remote-tracking branch 'upstream/dev' into dev 2016-01-26 20:23:48 +01:00
magnusknutas
1f6f2de9c6 Sorry, I was wrong they are needed 2016-01-26 19:37:53 +01:00
Martin Hjelmare
b13e48bd71 Merge pull request #999 from MartinHjelmare/fix-not-adding-devices
Fix adding devices on the fly
2016-01-26 19:30:31 +01:00
magnusknutas
9a79ecf2d3 Remove warnings no longer needed (I think :D) 2016-01-26 19:25:41 +01:00
magnusknutas
ebd475b380 Unused import was the problem 2016-01-26 18:50:25 +01:00
magnusknutas
cff77a175d Year periodic test (just to trigger travis but should maybe be here anyway) 2016-01-26 18:44:41 +01:00
magnusknutas
726637b867 New and improved handling of the matching! Kudos to @balloob 2016-01-26 18:37:19 +01:00
MartinHjelmare
9117fa6eb8 Fix adding devices on the fly
Devices were not added without persistence enabled and restart of HA.
Node id was added to defaultdict(list) by mistake when checking if list
of defaultdict was empty.

* Fix adding devices in mysensors_callback.
* Change devices to regular dict.
2016-01-26 18:12:13 +01:00
magnusknutas
6ae57b5aaf Some warnings if input is wrong for periodic tasks 2016-01-26 17:35:12 +01:00
magnusknutas
e8e2814313 Warnings for not divisible settings 2016-01-26 17:30:55 +01:00
magnusknutas
a918be517d test past midnight support for hours 2016-01-26 17:13:18 +01:00
magnusknutas
ec5d88b98e hourly periodic task 2016-01-26 17:06:50 +01:00
magnusknutas
13fbefcdf8 Running periodic tasks 2016-01-26 10:28:31 +01:00
Stefan Jonasson
f4c9540a1b Updated teh entity_id generation
Entity ids will now look like this:
sensor.philio_technology_corporation_psm021_slim_multisensor_doorwindow_sensor_7
sensor.philio_technology_corporation_psm021_slim_multisensor_luminance_7
sensor.philio_technology_corporation_psm021_slim_multisensor_motion_sensor_7
sensor.philio_technology_corporation_psm021_slim_multisensor_tamper_sensor_7
sensor.philio_technology_corporation_psm021_slim_multisensor_temperature_7

sensor.philio_technology_corporation_psm021_slim_multisensor_doorwindow_sensor_8
sensor.philio_technology_corporation_psm021_slim_multisensor_luminance_8
sensor.philio_technology_corporation_psm021_slim_multisensor_motion_sensor_8
sensor.philio_technology_corporation_psm021_slim_multisensor_tamper_sensor_8
sensor.philio_technology_corporation_psm021_slim_multisensor_temperature_8

And if there is several values of the same type in a node the instance id will be appended like this (three switches in the same device):
switch.fibaro_system_unknown_type0202_id1002_switch_9
switch.fibaro_system_unknown_type0202_id1002_switch_9_2
switch.fibaro_system_unknown_type0202_id1002_switch_9_3
2016-01-26 09:33:26 +01:00
Fabian Affolter
e72f61ce73 Update docstring 2016-01-26 09:11:04 +01:00
Paulus Schoutsen
4c056db3bb Fix Chrome url sync 2016-01-25 23:56:43 -08:00
Paulus Schoutsen
c10a86d1bf Merge branch 'master' into dev
Conflicts:
	homeassistant/const.py
2016-01-25 22:58:06 -08:00
Paulus Schoutsen
b97de5cef6 Merge pull request #1000 from balloob/view-url-sync
Update frontend with view url sync
2016-01-25 22:54:00 -08:00
Paulus Schoutsen
4233c0bc66 Update frontend with view url sync 2016-01-25 22:47:13 -08:00
Paulus Schoutsen
b13008201e Fix lint issue 2016-01-25 21:31:21 -08:00
Paulus Schoutsen
125ad8630d Add views to demo 2016-01-25 21:27:36 -08:00
Paulus Schoutsen
5c3ad5d4c0 Update frontend 2016-01-25 17:57:33 -08:00
Paulus Schoutsen
414aa8563d Merge pull request #930 from TangoAlpha/dev
Add LIFX bulb support
2016-01-25 17:51:47 -08:00
happyleavesaoc
d08a181c72 Merge pull request #994 from happyleavesaoc/orvibo_bump
Orvibo version bump
2016-01-25 19:34:21 -05:00
happyleavesaoc
80c6bf6744 bump orvibo version 2016-01-25 16:40:27 -05:00
Tim
b1ba792715 Brightness from HA overrides brightness from HSV conversion 2016-01-25 21:19:27 +00:00
Per Sandström
b99f6c1a46 Merge pull request #995 from persandstrom/vsure0.4.8
update python-verisure to version 0.4.8
2016-01-25 22:04:02 +01:00
Tim
1397f9e588 Fix logging typo 2016-01-25 20:58:10 +00:00
Tim
bb7f92330d Update liffylights version 2016-01-25 20:53:21 +00:00
Per Sandström
7cbd780748 update vsure version 2016-01-25 21:39:41 +01:00
Tim
784fea2d56 Update for new liffylights version 2016-01-25 19:08:48 +00:00
Tim
97f0425252 Update requirements_all 2016-01-25 15:20:23 +00:00
Tim
11120a8743 Fix liffylights import 2016-01-25 15:19:50 +00:00
Tim
8009542b3e Add extra debugging
Add duration to power on
2016-01-25 13:30:52 +00:00
Tim
32cfa6998c Update requirements_all 2016-01-25 12:13:24 +00:00
Tim
d10a5cf5e9 Update to v0.8.3 of liffylights 2016-01-25 11:54:41 +00:00
Tim
4089a7a0d3 Update requirements_all 2016-01-25 10:02:03 +00:00
Tim
5a926913d3 Update to v0.6 of liffylights 2016-01-25 10:00:15 +00:00
Paulus Schoutsen
6c91831baa Hide groups when they are views or auto defined 2016-01-24 23:45:06 -08:00
Tim
069dafa3a5 Update requirements_all 2016-01-25 07:38:37 +00:00
Paulus Schoutsen
4abc5c97cd Merge pull request #988 from auchter/thermostat-fan
Add service to control a thermostat's fan
2016-01-24 23:24:32 -08:00
Paulus Schoutsen
bb3dd47088 Merge pull request #992 from balloob/sections
Add support for sections in the frontend [Fixes #100705168]
2016-01-24 22:58:27 -08:00
Paulus Schoutsen
f6c53896e3 Allow groups to be used as views 2016-01-24 22:54:45 -08:00
Paulus Schoutsen
ad2e2d916b Merge pull request #979 from rmkraus/automation-decorator
Automation Decorator for custom components
2016-01-24 21:22:19 -08:00
Ryan Kraus
8406f81811 Removed decorator callback
The decorator callback was not actually necessary so it was removed and
replaced with a partial function instead.
2016-01-25 00:14:16 -05:00
Ryan Kraus
bcdfc555e0 Removed service decorator from event decorators 2016-01-24 23:09:09 -05:00
Ryan Kraus
3b89102338 Fixed lint issue from merge
extract_entity_ids from the service helpers was overwriting the service
decorator with one of its attributes. This was fixed.
2016-01-24 23:00:43 -05:00
Ryan Kraus
60dd2d441d Merge remote-tracking branch 'balloob/dev' into automation-decorator
# Conflicts:
#	homeassistant/helpers/service.py
#	tests/helpers/test_service.py
2016-01-24 22:51:00 -05:00
Ryan Kraus
5830da63b1 Moved service decorator to service helpers
Moved the service decorator to the service helpers module and moved the
associated tests.
2016-01-24 22:46:30 -05:00
Tim
50561ffe97 Fix long line 2016-01-25 03:39:13 +00:00
Tim
74e8446556 Bump version of liffylights 2016-01-25 03:34:24 +00:00
Tim
d94db5388c Add preliminary support for transition time 2016-01-25 03:32:55 +00:00
Ryan Kraus
f66aeb2e73 Added event helper tests
1. Added tests for all event decorators
2. Added tests for sunrise and sunset event helpers
2016-01-24 22:23:56 -05:00
Ryan Kraus
54b82ecd91 Lint fixes and additions to event decorators
1. service decorator was overwriting the function name with one of its
arguments.
2. Accidentally left an extra argument in track_sunrise.
3. Added track_utc_time_change decorator.
2016-01-24 21:06:15 -05:00
Ryan Kraus
2fa98167c2 Updated example.py component
Cleaned up example.py to better handle failed loads.
2016-01-24 20:05:40 -05:00
Ryan Kraus
57725136c0 Many updates regarding event decorators
1. Added HASS to the arguments for callbacks that are created with
event decorators.
2. Added a service decorator.
3. Updated example.py in the example config to use the event decorators.
2016-01-24 19:52:22 -05:00
Ryan Kraus
40dbeb0b60 Another revision on event decorators
This revision of event decorators removes much of the complexity. The
decorated functions are no longer wrapped with a class that tracks
last_run, etc. Bootstrap now gives hass to the event_decorators module
before initializing components so the decorators no longer require
activation.
2016-01-24 17:46:05 -05:00
Ryan Kraus
ef92940ffb A few lint fixes to event decorators. 2016-01-24 16:45:35 -05:00
Erik
e7865c1d67 Merge pull request #990 from molobrakos/squeezebox-fix
bugfix for squeezebox cover art regression
2016-01-24 21:56:21 +01:00
Ryan Kraus
02e634c6a2 Fixed bugs to allow HA to boot again
1) helpers/event should not import the sun component unless it is
requested. This prevents circular import.
2) fixed import typo in bootstrap
2) bootstrap cannot import event_decorators until it is needed because
this leads to a circular import.
2016-01-24 15:55:47 -05:00
Ryan Kraus
0f937cad74 Initial pass at event decorators
Created event decorators for custom components. Decorators were created
for the events: track_state_change, track_sunrise, track_sunset, and
track_time_change.
2016-01-24 15:28:09 -05:00
Ryan Kraus
81dd1515ae Moved sunrise/sunset tracking to helpers
The automation component contained some very handy and generic
functions for tracking sunset and sunrise. This was moved to
helpers/event.py.
2016-01-24 15:07:09 -05:00
Ryan Kraus
a65d0f0549 Reverting Automation decorator in favor of a new approach. 2016-01-24 14:44:48 -05:00
Michael Auchter
881c82c2df nest: implement fan control 2016-01-24 12:47:21 -06:00
Michael Auchter
df94c909f7 thermostat: add service to control fan mode 2016-01-24 12:47:21 -06:00
Michael Auchter
a0ed469aa2 thermostat: move fan attribute up to thermostat 2016-01-24 12:47:20 -06:00
Michael Auchter
afa4fc4ef5 thermostat: split up services 2016-01-24 12:47:20 -06:00
Paulus Schoutsen
df450c3d1f Merge pull request #987 from balloob/update-pynetgear
Update version pynetgear
2016-01-24 09:46:23 -08:00
Paulus Schoutsen
dc5d652d31 Update version pynetgear 2016-01-24 09:43:06 -08:00
Paulus Schoutsen
e4fe19fff0 Merge pull request #986 from turbokongen/dev
Small errorlog fix.
2016-01-24 08:54:54 -08:00
John Arild Berentsen
f6f3f54228 flake8 complaint fix 2016-01-24 16:43:24 +01:00
John Arild Berentsen
6df67d2852 Send correct command to pyrfxtrx
Although it seems to work with send_on, it throws an logged error. Using correct command against pyrfxtrx removes this error.
2016-01-24 16:37:38 +01:00
Tim
2411d1f2c8 Fix wrongly generated requirements 2016-01-24 10:07:56 +00:00
Tim
9f6a1c75fa Fix wrongly generated requirements 2016-01-24 10:01:23 +00:00
Stefan Jonasson
1842e5909e Updated the entity names for all z-wave devices.
Z-Wave have a unique node id, this is now added to the entity name.

This is a change will break old configs.  The Z-Wave entity_id will have to be updated in configurations.

The reason for this change is that the old behavior created duplicate entity_ids if you have several similar devices. The order devices is included is not consistent so when the service was restarted the entity ids could change and the configuration for entities will be invalid.

There was also inconsistencies between the naming for sensors and switches.
(cherry picked from commit 33a78df)
2016-01-24 10:20:51 +01:00
John Arild Berentsen
6f31aacb90 Merge pull request #1 from balloob/dev
Updating base
2016-01-24 09:58:51 +01:00
Paulus Schoutsen
e541b9ba77 Merge pull request #981 from balloob/ordered-yaml
Load YAML config into an ordered dict
2016-01-23 23:04:41 -08:00
Paulus Schoutsen
53484e46a3 Move generate_entity_id to entity helpers 2016-01-23 23:00:46 -08:00
Paulus Schoutsen
de79a46d43 Move extract_entity_id to service helpers 2016-01-23 22:57:14 -08:00
Paulus Schoutsen
bc19ef66bf Move split_entity_id to helpers 2016-01-23 22:49:49 -08:00
Paulus Schoutsen
de08f0afaa Load YAML config into an ordered dict 2016-01-23 22:41:01 -08:00
Tim
706bbeae16 Add to .coveragerc 2016-01-24 02:17:52 +00:00
Paulus Schoutsen
e5497d89f4 Merge pull request #980 from balloob/upgrade-cast
Upgrade PyChromecast version
2016-01-23 18:15:12 -08:00
Paulus Schoutsen
048ec0aa66 Merge pull request #964 from balloob/passive-zones
Allow passive zones
2016-01-23 18:15:06 -08:00
Paulus Schoutsen
03ed85b0a7 Merge pull request #971 from trollkarlen/smtp-fix
smtp: make smtp component thread safe
2016-01-23 18:08:03 -08:00
Paulus Schoutsen
9d92707fd7 Merge pull request #973 from turbokongen/dev
Added support for Dimming with lights in Rfxtrx module
2016-01-23 18:03:37 -08:00
Paulus Schoutsen
90c392e270 Upgrade PyChromecast version 2016-01-23 17:29:40 -08:00
Tim
6cb6cbfefd Update requirements 2016-01-24 01:18:18 +00:00
Tim
6d2bca0fd1 Import 3rd party library inside method 2016-01-24 01:13:51 +00:00
Tim
99286391e1 Fixes for lint 2016-01-24 01:00:02 +00:00
Tim
711f2da496 Update liffylights version 2016-01-24 00:43:50 +00:00
Ryan Kraus
fbd68b6f89 Created automation decorator prototype
Created an initial iteration of an Automation decorator.
2016-01-23 19:39:59 -05:00
Paulus Schoutsen
85ebd0ab59 Merge pull request #977 from sdague/dev
fix typo in log message
2016-01-23 14:27:37 -08:00
Tim
58034219b6 Bump version liffylights 2016-01-23 22:23:46 +00:00
Tim
17f5a466d9 Separate LIFX code and HA component 2016-01-23 22:14:57 +00:00
Sean Dague
90ca6a0998 fix typo in log message
The plex component logs an htts url, which is confusing to people, as
they think something is broken, when it is not.

Closes #959
2016-01-23 16:06:50 -05:00
Erik
ec2b433733 should be _id 2016-01-23 18:55:43 +01:00
Erik
492c4b7f00 style 2016-01-23 18:14:03 +01:00
Erik
b3beb9f3c9 style 2016-01-23 18:08:54 +01:00
Erik
837e7affa7 only query artwork by track_id if id is available (7.7 vs 7.9 version issue?) 2016-01-23 17:48:14 +01:00
turbokongen
6d527842dd Another flake8 fix too long line 2016-01-23 12:14:00 +01:00
turbokongen
e30915eb2c flake8 complaint fix 2016-01-23 12:08:21 +01:00
turbokongen
43e2b58f20 Fixing of various test errors 2016-01-23 12:00:03 +01:00
turbokongen
d3c6c892a8 Small intendation fix 2016-01-23 11:50:09 +01:00
turbokongen
a63edcf505 Added support for Dimming with lights in Rfxtrx module 2016-01-23 11:18:11 +01:00
Robert Marklund
0e7088ce3b smtp: make smtp component thread safe
Also fix so it does not require ip, port, username and password when using local smtp server.
Add debug config.

Signed-off-by: Robert Marklund <robbelibobban@gmail.com>
2016-01-23 10:43:10 +01:00
Paulus Schoutsen
0042e7725d Merge pull request #967 from HydrelioxGitHub/NetAtmo-icons
Add icons to NetAtmo sensors
2016-01-22 12:56:43 -08:00
hydreliox
613f8d0bd2 Add icons to NetAtmo sensors 2016-01-22 21:15:07 +01:00
Greg Dowling
61ca1ab2c1 Merge pull request #948 from balloob/sensor_template
First draft of sensor.template.
2016-01-22 16:49:49 +00:00
pavoni
ad62591f43 Change error state to be 'error' rather than 'unknown', trace error. 2016-01-22 16:30:02 +00:00
Paulus Schoutsen
5ba33bc40e Merge pull request #950 from michaelkuty/dev
Add statsd component.
2016-01-22 07:41:04 -08:00
Michael Kutý
143b08d661 Add statsd component. 2016-01-22 16:18:15 +01:00
pavoni
87a9fd8252 Handle race condition on startup. 2016-01-22 11:30:04 +00:00
pavoni
b1f7b5c6d7 Tidy, add test for ValueException logic. 2016-01-22 09:37:20 +00:00
Paulus Schoutsen
bb97af1504 Allow passive zones 2016-01-22 00:00:36 -08:00
Paulus Schoutsen
9a092654e9 Merge pull request #962 from FreekingDean/dev
Adding Support for Insteon Hub
2016-01-21 23:31:16 -08:00
Dean
d59b98ee2b Added Insteon Support 2016-01-22 02:28:36 -05:00
Paulus Schoutsen
6bbbbd9e17 Update mdi icons 2016-01-21 23:07:43 -08:00
Paulus Schoutsen
9fbedd8b5f Merge pull request #963 from balloob/scrip-service-helper
Script: use config service helper
2016-01-21 22:55:28 -08:00
Paulus Schoutsen
a91163877f Script: use config service helper 2016-01-21 22:47:25 -08:00
Paulus Schoutsen
0acce86596 Merge pull request #958 from balloob/itunes-updates
iTunes Updates
2016-01-21 18:44:55 -08:00
William Scanlon
c95b03f240 Fixed style 2016-01-21 18:32:10 -08:00
William Scanlon
56ce3e5f5a Wink siren support 2016-01-21 18:32:10 -08:00
pavoni
8b5751ad44 Merge branch 'dev' into sensor_template 2016-01-21 23:17:53 +00:00
pavoni
c615272c06 Tidy. 2016-01-21 23:17:19 +00:00
Jon Maddox
a3b8122707 not using these 2016-01-21 17:54:26 -05:00
Jon Maddox
682e3460e0 use custom material icons to represent speaker state 2016-01-21 17:45:30 -05:00
Jon Maddox
71dbd10b39 let port be optional 2016-01-21 17:18:52 -05:00
Paulus Schoutsen
7f143bcdf9 Merge pull request #957 from trollkarlen/yr.no-fix
yr.no: fix some bugs in the component
2016-01-21 13:33:36 -08:00
Robert Marklund
f19a46dcfe yr.no: fix some bugs in the component
Someone forgot some in:s and ()
Also added windGust sensor.

Signed-off-by: Robert Marklund <robbelibobban@gmail.com>
2016-01-21 22:04:18 +01:00
Paulus Schoutsen
625f69443a Merge pull request #953 from trollkarlen/kodi-fix
kodi: remove traceback on state check
2016-01-21 12:18:28 -08:00
Robert Marklund
0bdd293572 kodi: remove traceback on state check
If the computer is sleeping that are running xbmc then
the traceback is printed in the log all the time.
Keep the traceback in debug mode of the module if needed.

Signed-off-by: Robert Marklund <robbelibobban@gmail.com>
2016-01-21 20:59:58 +01:00
pavoni
f8072aae68 Merge branch 'dev' into sensor_template 2016-01-21 18:34:13 +00:00
pavoni
92afcae9be Add test. 2016-01-21 18:31:44 +00:00
pavoni
a6f37c032b Revise to not need dependencies (or quoting)! 2016-01-21 17:35:33 +00:00
pavoni
e66d15b71d First drafy of sensor.template. 2016-01-21 16:31:23 +00:00
Paulus Schoutsen
1098194a89 Merge pull request #937 from trollkarlen/sonos-multicast
sonos: add hosts and interface_addr to sonos config
2016-01-21 07:30:40 -08:00
Paulus Schoutsen
66d23cd15f Merge pull request #945 from zmrow/fix-exception-in-openweather
Fix #863 - Added a check for good pull from OWM and return if the object is None
2016-01-20 19:57:57 -08:00
zmrowicki@hotmail.com
c3a1193ef9 Fix #863 - Added a check for good pull from OWM and return if the object is None 2016-01-20 19:36:33 -08:00
Paulus Schoutsen
ec2d9af8dc Merge pull request #943 from trollkarlen/influxdb-fix
influxdb: fix the need of admin to run
2016-01-20 19:22:40 -08:00
Paulus Schoutsen
f0e44728d7 Merge pull request #944 from HydrelioxGitHub/twitter_notify
Add Twitter as a notification platform
2016-01-20 19:21:38 -08:00
hydreliox
f67747456f Clean some errors 2016-01-21 02:14:11 +01:00
hydreliox
f6017a17b2 Add Twitter as a notification platform 2016-01-21 02:02:32 +01:00
Robert Marklund
89aa3cbc62 influxdb: fix the need of admin to run
Use select statment to show if db exits instead of
'SHOW DATABASES' which cant be run by a non admin user.
See https://github.com/influxdata/influxdb/issues/4785 for more info.

Also influxdb dont like empty writes('') so ignore state changes of that kind,
this happens on startup of home assistant.

Signed-off-by: Robert Marklund <robbelibobban@gmail.com>
2016-01-21 00:19:16 +01:00
Robert Marklund
543190dfb0 sonos: add hosts and interface_addr to sonos config
Config can now specify one or more hosts to connect and
also a interface_addr to multicast on if multiple interfaces exists.

Signed-off-by: Robert Marklund <robbelibobban@gmail.com>
2016-01-20 22:55:08 +01:00
Paulus Schoutsen
fdc8c45a69 Merge pull request #940 from balloob/remove_sensor_state
Remove sensor state
2016-01-20 12:52:23 -08:00
pavoni
d2d421ca8f Remove ghost debug code. 2016-01-20 20:02:03 +00:00
pavoni
7ad5b3a17b Fix bug related to maker_param types 2016-01-20 19:13:29 +00:00
Paulus Schoutsen
c8dd9696b4 Merge pull request #939 from molobrakos/squeezebox-fix
artwork fixes
2016-01-20 09:16:32 -08:00
Erik
58ef69b95d less hacky way of getting unique cover art 2016-01-20 16:31:51 +01:00
pavoni
0de9229d75 More tidying. 2016-01-20 11:25:20 +00:00
pavoni
f4c3ac2a62 Tidy 2016-01-20 11:19:00 +00:00
pavoni
6c5ceaf686 Remove sensor state from switch and wemo 2016-01-20 11:06:08 +00:00
Erik
54f65ae87d 1) artwork_url might be a relative url (such as /imageproxy). in that case, join it with the base url. note: urllib.parse.urljoin will handle case when the artwork url is absolute. 2) artwork would not be replaced in the user interface because the url did not change between tracks (http://.../cover.jpg). solved by appending internal hash of the media title to the url to force reload 2016-01-20 10:57:39 +01:00
Paulus Schoutsen
4f2dc3cc2a Merge pull request #934 from balloob/mqtt-protocol
Allow forcing MQTT protocol v3.1
2016-01-19 20:20:04 -08:00
Paulus Schoutsen
a4ee2bd8ef Merge pull request #935 from balloob/0.11.1
0.11.1
2016-01-19 12:20:26 -08:00
MartinHjelmare
d4629a7efe Fix missing binary sensor types
* Add missing binary sensor types to sensor/mysensors.
* Remove unneeded pylint disable.
2016-01-19 12:13:34 -08:00
Paulus Schoutsen
441ae73344 Merge pull request #936 from MartinHjelmare/fix-mysensors-sensors
Fix missing binary sensor types
2016-01-19 12:11:33 -08:00
MartinHjelmare
9249dc6dd3 Fix missing binary sensor types
* Add missing binary sensor types to sensor/mysensors.
* Remove unneeded pylint disable.
2016-01-19 19:26:40 +01:00
Paulus Schoutsen
4c4539caff Version bump to 0.11.1 2016-01-19 09:12:03 -08:00
Paulus Schoutsen
4c0ff0e0d0 Allow forcing MQTT protocol v3.1 2016-01-19 09:07:54 -08:00
Paulus Schoutsen
1ceee2d6c5 Fix MQTT reconnecting 2016-01-19 09:07:35 -08:00
Paulus Schoutsen
cbb74d50ce Enforce entity attribute types 2016-01-19 09:07:27 -08:00
Erik
c2d72bbf09 fix issue where sensors and switches were duplicated because of component getting initialized twice. closes #913 2016-01-19 09:06:57 -08:00
Paulus Schoutsen
f97ba263c4 Allow forcing MQTT protocol v3.1 2016-01-19 09:00:40 -08:00
Paulus Schoutsen
85df9e98bd Merge pull request #922 from balloob/mqtt-fixes
Fix MQTT reconnecting
2016-01-19 08:45:49 -08:00
Paulus Schoutsen
2faafb9c0f Merge pull request #931 from molobrakos/fix-tellduslive-duplicates
proper initialization
2016-01-18 18:01:12 -08:00
Erik
d43101f22a fix issue where sensors and switches were duplicated because of component getting initialized twice. closes #913 2016-01-18 19:41:41 +01:00
Tim
3d23cd10fc Attempt to fix ungrouped-imports pylint error 2016-01-18 18:30:09 +00:00
Tim
66cd8d264e Remove use of warn() 2016-01-18 18:27:46 +00:00
Tim
291910d74e Add LIFX bulb support 2016-01-18 18:10:32 +00:00
Paulus Schoutsen
7ac648d0ab Merge pull request #889 from Bart274/dev
Create command_sensor.py
2016-01-18 07:47:00 -08:00
Bart274
3b42390062 Update command_sensor.py 2016-01-18 13:48:09 +01:00
Bart274
a80917f530 Update command_sensor.py 2016-01-18 09:24:38 +01:00
Paulus Schoutsen
8fbb585874 Fix MQTT reconnecting 2016-01-17 21:39:25 -08:00
Ryan Kraus
027d97321f Merge pull request #911 from rmkraus/service-toggle
Added toggle service
2016-01-17 21:11:35 -05:00
Paulus Schoutsen
62a1c9687e Merge pull request #920 from balloob/enforce-entity-types
Enforce entity attribute types
2016-01-17 18:01:20 -08:00
Paulus Schoutsen
a9c6f8c1d9 Enforce entity attribute types 2016-01-17 17:50:20 -08:00
Paulus Schoutsen
06b4fcc2cf Fix lint errors 2016-01-17 16:36:25 -08:00
Paulus Schoutsen
6dc2501116 ps - fix RPi GPIO imports 2016-01-17 16:29:41 -08:00
Paulus Schoutsen
54060f27ef Merge pull request #893 from joshughes/nest_sensor
Add the nest sensor for tracking data from nest
2016-01-17 15:16:25 -08:00
Ryan Kraus
85aa4fdd2e Revised entity toggle to use is_on
The toggle function in the Entity ABC was using state == STATE_ON to
determine whether the entity was on. This was revised to use the is_on
property instead.
2016-01-17 16:59:22 -05:00
Ryan Kraus
0624445627 Added tests for toggle service.
1) Added tests to toggle service.
2) Removed color_util import in light tests. It was not being used.
2016-01-17 16:42:18 -05:00
Joseph Hughes
4ca4941c82 fix pylint errors in sensor/nest 2016-01-16 12:56:38 -07:00
Joseph Hughes
4dbd84ead0 make sure everything inherits from NesSensor 2016-01-16 12:53:42 -07:00
Joseph Hughes
0495776a22 merge upstream dev 2016-01-16 12:52:22 -07:00
Joseph Hughes
dd35551047 fix order of inhertiance 2016-01-16 12:47:08 -07:00
Paulus Schoutsen
9f61369156 Version bump to 0.12.0.dev0 2016-01-16 11:23:55 -08:00
Paulus Schoutsen
bd536be66d Merge pull request #883 from balloob/dev
0.11.0
2016-01-16 11:23:40 -08:00
Paulus Schoutsen
c5b69a0ee4 Update version to 0.11 2016-01-16 11:20:02 -08:00
Paulus Schoutsen
206b3a88a2 Merge pull request #910 from rmkraus/nginx-config
Added nginx sample configuration
2016-01-16 09:44:42 -08:00
Paulus Schoutsen
880bd011a9 Merge pull request #908 from persandstrom/verisure_code_digits
Verisure - code digits settings
2016-01-16 09:04:53 -08:00
Paulus Schoutsen
7de91a270a Merge pull request #905 from Xorso/alarmdotcom_bugfix
Alarmdotcom bugfix
2016-01-16 08:48:07 -08:00
Paulus Schoutsen
09973abe8a Merge pull request #897 from balloob/input-boolean
New component: input_boolean
2016-01-16 08:47:29 -08:00
Ryan Kraus
342a819fd4 Added toggle service
Added a toggle service to the homeassistant, light, switch, and
media_player domains.
2016-01-16 10:45:05 -05:00
Ryan Kraus
4bf185c868 Added nginx sample configuration
Added a sample nginx configuration with instructions detailing how to
setup a very secure HTTPS server for HA that servers over standard
ports without requiring HA to run as root.
2016-01-16 10:17:26 -05:00
Per Sandström
78742c016b code digits settings 2016-01-16 15:12:54 +01:00
Greg Dowling
1ed314f6f6 Merge pull request #906 from balloob/bump_vera_version
Bump pywemo version.
2016-01-16 11:40:10 +00:00
pavoni
be23c6c86d Bump pywemo version. 2016-01-16 11:35:17 +00:00
Daren Lord
a194c4f1bd Merge branch 'dev' of https://github.com/balloob/home-assistant into alarmdotcom_bugfix 2016-01-15 22:16:01 -07:00
Daren Lord
edb24add6b Fixed but when alarm is trying to change state the state gets updated and changes the page. 2016-01-15 22:15:31 -07:00
Paulus Schoutsen
bc88985889 Merge pull request #901 from philipbl/ssl
Increase security of using SSL
2016-01-15 17:32:51 -08:00
Greg Dowling
3d27dd3ec4 Merge pull request #902 from balloob/fix_vera_sensor_subscriptions
Another Vera fix
2016-01-15 21:46:27 +00:00
pavoni
c2f5eb3073 Missed change. 2016-01-15 21:42:51 +00:00
pavoni
541b268721 Update to use refactored pyvera, fixes subscription issues - esp sensors. 2016-01-15 21:30:58 +00:00
Philip Lundrigan
e77daed086 Merge pull request #900 from philipbl/fix_locative
Fix bug in Locative logic
2016-01-15 13:43:52 -07:00
Philip Lundrigan
fdbb409331 Increase security of using SSL 2016-01-15 13:39:54 -07:00
Philip Lundrigan
f96c5aa62f Fix bug in locative logic 2016-01-15 13:19:53 -07:00
Paulus Schoutsen
c07a096e57 Merge pull request #899 from sfam/dev
Update RPi.GPIO version and code refactoring
2016-01-15 11:56:27 -08:00
sfam
48b6c5b5cb fix import BinarySensorDevice 2016-01-15 18:14:46 +00:00
sfam
d8d59d9a66 remove rpi_gpio sensor 2016-01-15 18:05:48 +00:00
sfam
702dddbb2f update requirements_all 2016-01-15 17:28:32 +00:00
sfam
127488004c update coveragerc and requirements_all 2016-01-15 17:16:02 +00:00
sfam
7c925ac295 update comments 2016-01-15 16:53:46 +00:00
sfam
8617b92d1b Update RPi.GPIO version and code refactoring 2016-01-15 16:53:45 +00:00
Paulus Schoutsen
bb80e3a9fc Merge pull request #898 from balloob/vera_fixes
Vera fixes
2016-01-15 07:43:03 -08:00
Joseph Hughes
80023f62d9 Merge remote-tracking branch 'upstream/dev' into nest_sensor
# Conflicts:
#	.coveragerc
#	requirements_all.txt
2016-01-15 08:18:16 -07:00
Joseph Hughes
9617288bd5 multiple inheritance for nest binary sensor 2016-01-15 08:16:33 -07:00
pavoni
4fd79afa42 Bump pyvera version. 2016-01-15 11:45:17 +00:00
pavoni
719f9a63d9 Fix style issue 2016-01-15 10:52:58 +00:00
pavoni
4bc33d0352 Bump pyvera version. 2016-01-15 10:34:14 +00:00
pavoni
c4e1035638 Add humidity sensor, add units for humidity and light sensors. 2016-01-15 09:21:48 +00:00
Bart274
d40e889d3b Update command_sensor.py
Importing CommandSensorData from the command_sensor in sensors in order not to duplicate code
2016-01-15 09:01:58 +01:00
Bart274
41acc8fa43 Update .coveragerc
This shouldn't be excluded according to @balloob because it's a pure python component
2016-01-15 08:59:11 +01:00
Fabian Affolter
90c2aed7b4 Update docstrings 2016-01-15 08:55:59 +01:00
Fabian Affolter
5108602de3 Update link to docs and docstrings 2016-01-15 08:55:59 +01:00
Paulus Schoutsen
5ee17ffc58 Update frontend 2016-01-14 23:47:55 -08:00
Paulus Schoutsen
6b899ddc1d 100% test coverage for input_boolean 2016-01-14 23:25:25 -08:00
Paulus Schoutsen
0a711922ef Remove unnecessary instance variable 2016-01-14 23:19:08 -08:00
Paulus Schoutsen
475b631d9c Initial version input_boolean 2016-01-14 23:18:52 -08:00
Paulus Schoutsen
40c75f0a51 Merge pull request #887 from sdague/dev
Add support for Proliphix thermostat
2016-01-14 22:03:09 -08:00
Paulus Schoutsen
1dd99a6d5d Update frontend 2016-01-14 22:02:02 -08:00
Paulus Schoutsen
bed9b038c8 Merge pull request #856 from w1ll1am23/Wink_power_strip_support
Wink power strip support
2016-01-14 19:23:41 -08:00
Joseph Hughes
9210c57c2d Fix lint errors and PR comments 2016-01-14 14:32:43 -07:00
Joseph Hughes
313cbda0aa fix multiple PR issues 2016-01-14 14:17:28 -07:00
Paulus Schoutsen
86b0e49995 Merge pull request #894 from Xorso/alarmdotcom
Adding in alarm control panel for Alarm.com accounts.
2016-01-14 13:01:58 -08:00
Joseph Hughes
a39148dd38 fix pylint errors for Nest Sensor 2016-01-14 11:37:17 -07:00
Joseph Hughes
b8c8c71b78 run requirements_all again for nest sensor 2016-01-14 11:28:28 -07:00
Joseph Hughes
65a3bf2325 fix merge error 2016-01-14 11:01:53 -07:00
Joseph Hughes
ac34db3c8a Merge remote-tracking branch 'upstream/dev' into nest_sensor
# Conflicts:
#	.coveragerc
#	requirements_all.txt
2016-01-14 11:00:34 -07:00
Joseph Hughes
f2e86ecd8e Merge remote-tracking branch 'upstream/master' into nest_sensor
# Conflicts:
#	homeassistant/const.py
2016-01-14 10:52:19 -07:00
Joseph Hughes
7b993da0de address PR comments for Nest Sensor 2016-01-14 10:48:24 -07:00
William Scanlon
de5bee6359 Updated python-wink version 2016-01-14 08:56:59 -05:00
Daren Lord
34f124190c Fixing pylint errors 2016-01-14 06:29:12 -07:00
Sean Dague
d867366be1 add proliphix thermostat support
The proliphix nt10e is an early network thermostat that supports an
HTTP interface. This adds basic support for it to home-assistant (get
/ set heating setback).
2016-01-14 06:32:08 -05:00
Bart274
d0d375d433 Update command_sensor.py 2016-01-14 10:27:11 +01:00
Bart274
d17aa103b4 Update command_sensor.py 2016-01-14 10:13:57 +01:00
Fabian Affolter
f0af23a4f5 Add link to docs and update pressure unit 2016-01-14 09:16:20 +01:00
Paulus Schoutsen
bdd6bb7918 Update frontend 2016-01-13 23:51:29 -08:00
Paulus Schoutsen
3ec49a0ef0 Merge pull request #892 from sdague/f_rounding
round min / max values for temperature
2016-01-13 22:50:15 -08:00
Daren Lord
7a2d049ce3 Removing LIFX from requirements_all.txt 2016-01-13 23:33:19 -07:00
Paulus Schoutsen
7fba4b354e Merge pull request #878 from HydrelioxGitHub/netatmo
Netatmo
2016-01-13 22:24:17 -08:00
Daren Lord
87cecd7e95 Adding to requirements_all 2016-01-13 23:22:42 -07:00
Daren Lord
303cb8e350 Adding alarmdotcom to coveragerc 2016-01-13 23:18:52 -07:00
Daren Lord
236ae94474 Merging dev branch 2016-01-13 23:14:58 -07:00
Daren Lord
308969e6dd Adding in alarm.com control panel. 2016-01-13 23:09:39 -07:00
hydreliox
4dd558a420 Update Requirements 2016-01-14 07:09:25 +01:00
Joseph Hughes
0e6a60b086 Add the nest sensor for tracking data from nest 2016-01-13 21:05:47 -07:00
Sean Dague
4fc0163139 round min / max values for temperature
In order for the polymer thermostat component to have sensible step
values the min / max values have to be round numbers. The current code
only does that for systems running in degrees C. For those of us in
silly land that still function in degrees F, this causes some
oddities in the UI.

Always round mix / max values to make it good no matter what
fundamental units we are in.
2016-01-13 21:22:56 -05:00
hydreliox
314d34a644 Update library lnetatmo requirements
Thanks to @rmkraus
2016-01-14 03:00:51 +01:00
Paulus Schoutsen
2745b0f99e Merge pull request #874 from xifle/zwave
Zwave inclusion / exclusion events
2016-01-13 12:47:39 -08:00
Bart274
c2e8646aed Update .coveragerc 2016-01-13 12:20:37 +01:00
Bart274
e5919c1bfe Update command_sensor.py 2016-01-13 12:19:20 +01:00
Bart274
c8961fcf99 Create command_sensor.py
This adds a binary command_sensor
2016-01-13 11:52:42 +01:00
hydreliox
58cee75c0e coverage and requirements updated 2016-01-13 09:06:16 +01:00
hydreliox
4f13236008 Merge remote-tracking branch 'refs/remotes/balloob/dev' into netatmo 2016-01-13 09:03:44 +01:00
Paulus Schoutsen
2a377a6125 Refactor syslog component for Windows users 2016-01-12 23:59:15 -08:00
hydreliox
058dba50cc Correct name using format instead of concatenation 2016-01-13 08:46:45 +01:00
Paulus Schoutsen
e6846e7eb9 Convert asuswrt user/pass to strings 2016-01-12 22:28:53 -08:00
Ryan Kraus
a0ddda4bc6 Updated frontend to newest commit 2016-01-12 22:15:23 -08:00
Ryan Kraus
57c0f96118 Renamed update_state to update in universal media player
Renamed update_state method in universal media player to update so that
it would be called by HA when the state was being published. Moved the
update_ha_state to a function inside of __init__. Updated the tests
accordingly.
2016-01-12 22:15:23 -08:00
Ryan Kraus
a84429538b Fixed attribute configuration handling in universal media player
Forced all parsed attribute configurations to be of length 2. Removed
entity_id=None option from entity lookups. Explicitly passed entity
lookup information to _entity_lkp.
2016-01-12 22:15:23 -08:00
Ryan Kraus
07953fb7e3 Removed dependencies property from universal media player
The dependencies property was only being called once by the __init__
method so it was removed and the code was moved to the __init__ method.
The tests were updated to reflect this.
2016-01-12 22:15:23 -08:00
Ryan Kraus
12da6f531e Removed property from universal media player
The active_child_state property was unnecessary as it was not being
referenced outside the class. This commit removes it and updates the
tests accordingly.
2016-01-12 22:15:22 -08:00
Ryan Kraus
769f5aafb7 Added should_poll = False to universal media player 2016-01-12 22:15:22 -08:00
Ryan Kraus
a1abab8ced Set universal media player to force refresh when updating HA 2016-01-12 22:15:22 -08:00
Ryan Kraus
270a998e3c Merged service calling method in universal media player 2016-01-12 22:15:22 -08:00
Ryan Kraus
d829497c3d Changed universal media player to keep service attrs in dict
Revised universal media player to keep service data in a dictionary
rather than passing it around as magic parameters.
2016-01-12 22:15:22 -08:00
Ryan Kraus
a8d5b0e5ec Made universal media player cache active player
Revised universal media player to cache the active player when updating
the state when any of the children change. Revised tests to accommodate
this change.
2016-01-12 22:15:22 -08:00
Ryan Kraus
85d732a45a Streamlined child state lookups in universal media player
1) Removed children property because it was only being used by one
method.
2) Removed option to return state as object from _entity_lkp as it was
no longer needed.
3) Used hass.states.get to get entity state objects.
4) Revised test to remove children property.
2016-01-12 22:15:22 -08:00
Ryan Kraus
8f3e8d29f0 Renamed SUPPORT_VOLUME_STEP flag in media_player
1) Renamed SUPPORT_VOLUME_BUTTONS to SUPPORT_VOLUME_STEP
2) Removed unused imports from tests.
2016-01-12 22:15:22 -08:00
Ryan Kraus
ee4543d739 Using call_from_config in Universal Media Player
Changed universal media player to use the call_from_config helper to
call services specified in the configuration file. This was done to
copy what is done in the Alexa and Automation components.
2016-01-12 22:15:22 -08:00
Ryan Kraus
59456f20fb Added tests to universal media player and fixed bug
1) Fixed universal media player to maintain specified child order when
checking for active child.
2) Added many tests to universal media player.
2016-01-12 22:15:22 -08:00
Ryan Kraus
4a1f609893 Lint fixes and faster updating to universal media player.
1) Many lint fixes.
2) Bound the Universal Media Player to its dependencies so that its
state will be updated when one of its dependencies is changed.
2016-01-12 22:15:22 -08:00
Ryan Kraus
36214c73ee Better handling of entity lookups in Universal media player.
Allowed the lookup function in the Universal Media Player to return
either a state object or the actual state of an entity during lookup.
2016-01-12 22:15:21 -08:00
Ryan Kraus
20a1025a8c Added active_child attribute to universal media players.
The entity of the first active child is now reported in the attributes
for a universal media player.
2016-01-12 22:15:21 -08:00
Ryan Kraus
ec85884d92 Added initial implementation of universal media player. 2016-01-12 22:15:21 -08:00
Paulus Schoutsen
22c01b956f Merge pull request #888 from balloob/lint-fixes
Lint fixes
2016-01-12 22:00:48 -08:00
Paulus Schoutsen
9cdf84dacf Update flake8 and pylint versions 2016-01-12 21:57:43 -08:00
Paulus Schoutsen
60f40800c4 Use mock HA for locative tests 2016-01-12 21:56:09 -08:00
Paulus Schoutsen
3b7b12bbd5 Make Flake8 happy 2016-01-12 21:53:27 -08:00
Paulus Schoutsen
9a1aad8e92 Merge pull request #882 from moonshot/mqtt-eventstream
Create mqtt eventstream component
2016-01-12 21:29:17 -08:00
Moonshot
6f398f59df Fix filtering of EVENT_CALL_SERVICE and EVENT_SERVICE_EXECUTED events 2016-01-12 22:57:03 -05:00
Moonshot
8ace656657 Create mqtt eventstream component 2016-01-12 22:56:26 -05:00
hydreliox
a4481efe07 Code cleanup 2016-01-13 04:26:40 +01:00
hydreliox
dc31ddbef2 Extract 'modules' as a constant 2016-01-13 04:19:27 +01:00
hydreliox
0c2fe4c5f3 Remove temp unit logic
HA should convert unit automatically
2016-01-13 04:16:25 +01:00
hydreliox
03febb81d3 Extract 'secret_key' as a constant 2016-01-13 03:45:43 +01:00
hydreliox
6f1a25d8d6 Add a configuration validation
All parameters are required
2016-01-13 03:43:10 +01:00
hydreliox
f182f7dccd Remove uncessary requirement check
HA already check the requirement before setup the platform
2016-01-13 03:29:17 +01:00
Fabian Affolter
31fcd230b1 Update docstrings 2016-01-12 11:30:12 +01:00
Paulus Schoutsen
1ad5cae98e Merge pull request #884 from partofthething/zwave-alarm
Added z-wave alarm sensors to list of devices that can be auto-detected and used
2016-01-11 23:52:06 -08:00
ntouran
5af4864326 ZWave alarm sensor cleanup (pylint fixes) 2016-01-11 23:27:53 -08:00
Paulus Schoutsen
ea8d278f8f Rename freesms to free_mobile 2016-01-11 21:05:32 -08:00
Paulus Schoutsen
88de9908ce Merge branch 'pr/879' into dev
Conflicts:
	requirements_all.txt
2016-01-11 21:03:52 -08:00
hydreliox
1bbecce5eb Add requirements and coverage exception 2016-01-11 21:03:22 -08:00
ntouran
2495226c87 Merge remote-tracking branch 'origin/dev' into zwave-alarm 2016-01-11 20:48:54 -08:00
ntouran
c1aa1fb0e0 First attempt at adding Z-wave COMMAND_CLASS_ALARM 2016-01-11 20:46:45 -08:00
Paulus Schoutsen
a5b198e2b8 Merge pull request #771 from MartinHjelmare/mysensors-component-switch
Mysensors component switch
2016-01-11 19:04:35 -08:00
xifle
cd669239ae Changed zwave node add/remove events to services 2016-01-11 22:39:28 +01:00
hydreliox
bc73a6829d Code formatting
Correct pylint errors
2016-01-11 09:30:32 +01:00
hydreliox
542b640ef0 FreeMobile Notify
First Commit for FreeSMS platform
2016-01-11 09:25:26 +01:00
hydreliox
7823fb9788 Merge remote-tracking branch 'refs/remotes/balloob/dev' into dev 2016-01-11 08:40:07 +01:00
hydreliox
bcb42674fc Corrections due to pylint tests 2016-01-11 06:57:31 +01:00
hydreliox
1f1a46a8bd Remove unused library
Remove the library used for dev
2016-01-11 05:35:26 +01:00
hydreliox
7b999e6cd1 Convert Temperature output
Convert temperature from celcius to fahrenheit if the HA config need it
2016-01-11 05:29:32 +01:00
hydreliox
c97a25cc86 Add configuration verification and sensors type
Generate an error in the configuration name of a module or a queried
sensor type is not correct.
Add support for co2 pressure and noise sensors
2016-01-11 05:21:38 +01:00
Greg Dowling
cdf24ec205 Merge pull request #872 from balloob/vera-debugging
Vera updates
2016-01-10 17:34:49 +00:00
Paulus Schoutsen
579f4d4cca Merge pull request #875 from xifle/dev
Owntracks transition events minor improvement
2016-01-10 09:16:27 -08:00
xifle
fce04e7ad0 Added inclusion / exclusion events
Events may be used to start inclusion / exclusion of zwave devices.
This is especially useful in the case of a Z-Wave stick without
"hardware" inclusion button.
2016-01-10 15:28:34 +01:00
xifle
6c94650603 Accept lower & upper case for owntracks 'home' region 2016-01-10 15:00:14 +01:00
pavoni
fca8ad5b0b Tidy. 2016-01-10 12:48:36 +00:00
pavoni
4b4fb038e3 Update for new library, slightly revise switch logic. 2016-01-10 12:30:47 +00:00
MartinHjelmare
0b3a66dd1a Merge branch 'dev' into mysensors-component-switch 2016-01-10 04:27:39 +01:00
MartinHjelmare
2d8cf7de44 Fix wrapper and S_BINARY and bump req. version
* Wrap existing SerialGateway instance instead of subclassing
	SerialGatewat class.
* Add S_BINARY in switch platform only in version 1.5 of mysenors api.
* Use version 0.4 of pymysensors.
* Show gateway port as state attribute.
2016-01-10 04:10:38 +01:00
Paulus Schoutsen
ad931eac60 Merge pull request #870 from balloob/update-pychromecast
Update PyChromecast dependency
2016-01-09 16:27:09 -08:00
Paulus Schoutsen
438e78610d Update PyChromecast dependency 2016-01-09 16:21:08 -08:00
Paulus Schoutsen
058f6f9303 Merge pull request #862 from balloob/alexa-service
Add calling service functionality to Alexa
2016-01-09 16:13:46 -08:00
Paulus Schoutsen
73cdf00512 More service helper tests 2016-01-09 16:01:27 -08:00
Paulus Schoutsen
12b5caed70 ps - strip entity IDs in service call 2016-01-09 15:51:51 -08:00
Paulus Schoutsen
18d2668e3b Merge pull request #869 from philipbl/mqtt-fix
Default to MQTT protocol v3.1.1
2016-01-09 15:27:33 -08:00
pavoni
af21f72d17 Update pyvera version. 2016-01-09 22:58:28 +00:00
pavoni
b64680e4a8 Revise to depend on vera subscription data updates, rather than talking to device. 2016-01-09 21:13:34 +00:00
Philip Lundrigan
9faedf0e67 Default to MQTT protocol v3.1.1 (fix #854) 2016-01-09 13:43:44 -07:00
Paulus Schoutsen
204c151113 Merge pull request #865 from fabaff/owm-update
Update pyowm to 2.3.0
2016-01-09 11:59:15 -08:00
Paulus Schoutsen
9b8256ec07 Merge pull request #867 from persandstrom/verisure_fix_lost_connection
Verisure fix lost connection
2016-01-09 11:47:39 -08:00
pavoni
d61eb93c03 Remove throttle doesn't play well with subscriptions. 2016-01-09 16:16:41 +00:00
Fabian Affolter
a2c6cde83d Update pyowm to 2.3.0 2016-01-09 13:20:51 +01:00
Paulus Schoutsen
825c91f0c3 Add calling service functionality to Alexa 2016-01-08 18:54:28 -08:00
Paulus Schoutsen
d406d7fa94 Merge pull request #860 from philipbl/fix-test
Fix Yr test
2016-01-08 12:53:09 -08:00
Philip Lundrigan
3db6faab4d Fix yr test 2016-01-08 13:30:16 -07:00
Per Sandström
15a046f20c update module version 2016-01-08 19:52:03 +01:00
Per Sandström
d3cd304f68 correced status text 2016-01-08 19:50:49 +01:00
Paulus Schoutsen
84fb96a42f Merge pull request #858 from balloob/remove-wemo-polling
Remove wemo polling
2016-01-08 08:19:33 -08:00
pavoni
5a1fed3980 Bump pywemo version. 2016-01-08 16:00:56 +00:00
pavoni
bb8af3a2d5 Bump pywemo version, turn off polling, tidy trace. 2016-01-08 16:00:26 +00:00
William Scanlon
2803631906 Updated python-wink to 0.4.0 2016-01-08 08:25:26 -05:00
hydreliox
4c47ed31ff Correction to import lnetatmo right 2016-01-08 06:19:03 +01:00
hydreliox
1ed574b2a0 Point lnetatmo library directly from github 2016-01-08 05:34:51 +01:00
Fabian Affolter
d69c1b848a Fix docstrings 2016-01-07 11:57:45 +01:00
Fabian Affolter
35c29dac3f Use mbar instead of hPa 2016-01-07 11:50:02 +01:00
Fabian Affolter
aac44f3a2b Use the same unit for pressure as for the forecast sensor 2016-01-07 11:48:42 +01:00
William Scanlon
17dd8ddc9a Added wink power strip support 2016-01-06 12:22:50 -05:00
hydreliox
a8b36d9baa Multiple Module Configuration
Handle multiple module (see configuration.yaml)
2016-01-06 04:36:04 +01:00
hydreliox
520a8d0d0d Add NetAtmo Platform
Alpha version of the platform.
API library is not yet on PyPI
2016-01-06 03:39:16 +01:00
Paulus Schoutsen
033bd30391 Merge pull request #844 from balloob/delay-group-switch-sensors
Fix calling turn_on for groups with mixed content
2016-01-05 08:55:58 -08:00
Paulus Schoutsen
99e14380fe Merge pull request #847 from molobrakos/patch-1
No need to call update() here
2016-01-05 08:55:48 -08:00
Erik
5576649d60 Update eliqonline.py 2016-01-04 21:36:39 +01:00
Erik
82cd2f4ed6 Update eliqonline.py 2016-01-04 21:31:42 +01:00
Erik
66f12afbb1 don't fail if error
don't fail if request for updated data raises exception in underlying library
2016-01-04 21:12:10 +01:00
Greg Dowling
431656bbcf Merge pull request #848 from balloob/fix-vera-bugs
Bump pyvera version to fix vera dimmer off bug, and avoid shutdown race condition
2016-01-04 19:34:17 +00:00
Erik
a174a06e5c No need to call update() here
This also fixes a problem where the sensor is left uninitialized when the energy meter temporarily has lost connection with the hub. This caused the ELIQ Online server to return HTTP error 400: "user have no current power data", which in turn caused the used eliq library to fail during JSON parsing (issue reported).
2016-01-04 19:25:52 +01:00
Fabian Affolter
0d0fdb0adf Remove configuration details 2016-01-04 16:26:11 +01:00
pavoni
5f8dd65acf Bump pyvera version 2016-01-04 11:24:24 +00:00
Paulus Schoutsen
601211f1d9 Sun docs cleanup 2016-01-04 00:15:44 -08:00
Paulus Schoutsen
8983a97c70 Fix calling turn_on for groups with mixed content 2016-01-03 21:25:15 -08:00
Paulus Schoutsen
2e899bd61c Merge pull request #843 from rhooper/multiple-hues
Support multiple hue hubs using a filename parameter.
2016-01-03 18:39:21 -08:00
Roy Hooper
d0a8a57ae4 Ensure filename arg is passed around everywhere it's needed. 2016-01-03 15:30:06 -05:00
Paulus Schoutsen
08aabd18ad New version frontend 2016-01-03 11:44:26 -08:00
Paulus Schoutsen
2478623ebc Merge pull request #840 from balloob/reproduce-group
Reproduce state upgrades
2016-01-03 11:36:45 -08:00
Paulus Schoutsen
736183e6f5 Fix bug in reproduce_state with complex state attributes 2016-01-03 11:27:30 -08:00
Paulus Schoutsen
31f2707b2f Merge pull request #841 from xifle/dev
Owntracks transition events
2016-01-03 11:19:02 -08:00
xifle
d244d3b599 Fixed flake8 style errors 2016-01-03 17:42:49 +01:00
xifle
82904c59ce Fixed code style 2016-01-03 17:12:11 +01:00
Paulus Schoutsen
f8b2570cb3 Group entities when reproducing a state 2016-01-03 02:32:09 -08:00
Paulus Schoutsen
6268840b55 Merge pull request #839 from philipbl/sun_fix
Fix for sun if condition
2016-01-03 00:27:33 -08:00
Philip Lundrigan
c9ff0ab7eb Fix for sun if condition 2016-01-03 01:03:53 -07:00
Paulus Schoutsen
cf1f9a29bf Merge pull request #833 from eiaro/dev
Added more sensors to Telldus Live
2016-01-02 17:05:54 -08:00
Paulus Schoutsen
48b1f8e137 Merge pull request #829 from joshughes/influx_hack
Interpolate what we are sending to influx so its not always a string
2016-01-02 16:59:31 -08:00
Ronny Eia
4b96a7c820 Untabified lines 2016-01-03 01:00:10 +01:00
Paulus Schoutsen
452ebdac8e Merge pull request #838 from balloob/travis-tweaks
Make CI erros more prominent
2016-01-02 15:46:03 -08:00
Paulus Schoutsen
7dc1499386 Make CI erros more prominent 2016-01-02 14:43:03 -08:00
Paulus Schoutsen
3ccc7787af Merge pull request #834 from R1chardTM/reference-fix
Fix reference known_devices.yaml in service description
2016-01-02 14:05:31 -08:00
Paulus Schoutsen
02879e79e6 Merge pull request #828 from sander76/honeywell_away_mode
Honeywell away mode
2016-01-02 14:05:20 -08:00
Richard
305c87a9c9 Fix reference known_devices.yaml 2016-01-02 16:01:58 -06:00
Ronny Eia
3abc78eef2 Added power sensor 2016-01-02 22:30:02 +01:00
Paulus Schoutsen
4a421e25b0 Simplify Rest sensors 2016-01-02 13:29:33 -08:00
Ronny Eia
86047eceb1 Added wind sensor 2016-01-02 22:28:15 +01:00
Ronny Eia
7edbb6aadc Added rain sensor 2016-01-02 22:21:04 +01:00
Roy Hooper
0361f37178 Support multiple hue hubs using a filename parameter. 2016-01-02 16:13:58 -05:00
sander
55c5d254d5 some more pylinting.. 2016-01-02 21:09:03 +01:00
sander
36f5caa214 more pylinting.. 2016-01-02 20:59:45 +01:00
sander
8c7898ed05 pylinting.. 2016-01-02 20:53:25 +01:00
sander
39de92960d line too long change 2016-01-02 20:27:40 +01:00
Paulus Schoutsen
6d35bdafee Merge pull request #832 from R1chardTM/device-tracker
Add service description device tracker
2016-01-02 10:34:05 -08:00
Paulus Schoutsen
217ffc215b Update PyNetgear version 2016-01-02 10:27:11 -08:00
xifle
cbd3860585 Merge branch 'dev' of https://github.com/balloob/home-assistant into dev 2016-01-02 18:56:14 +01:00
xifle
5804dde0e9 Enables the use of owntracks transition events
By using the configuration option "use_events:yes" in the device_tracker section,
only 'enter'/'leave' events are considered to calculate the state of a tracker device.
The home zone is defined as the owntracks region 'home'. Other regions may also be defined, the name of
the region is then used as state for the device. All owntracks regions, the 'Share' setting must be enabled in the app.
2016-01-02 18:26:59 +01:00
Joseph Hughes
e9b2cf1600 Ensure we send data to influx as float and not as a string value. 2016-01-02 10:24:23 -07:00
Richard
cdf2179b3e Describe device tracker see service 2016-01-02 10:54:26 -06:00
sander
8f2ca856c7 added return False 2016-01-02 11:56:07 +01:00
Greg Dowling
622a6deb04 Merge pull request #820 from balloob/vera-subscriptons
Add ability to respond to events from Vera hub
2016-01-02 10:15:14 +00:00
Paulus Schoutsen
a698bc477d Merge pull request #819 from rhooper/phue-deconz
Add support for deCONZ (Raspbee-GW hue-like API)
2016-01-01 22:21:48 -08:00
Roy Hooper
a36ae4b24a Reduce chatiness 2016-01-02 01:01:11 -05:00
sander
c703c89dbd implement away mode 2016-01-01 15:29:58 +01:00
sander
b3227e491b Merge remote-tracking branch 'refs/remotes/balloob/dev' into dev 2016-01-01 14:44:25 +01:00
happyleavesaoc
9e83a80215 Merge pull request #788 from happyleavesaoc/command_switch_state
add statecmd to command_switch
2015-12-31 18:43:02 -05:00
happyleaves
9c85702c87 combine ifs 2015-12-31 18:39:40 -05:00
Paulus Schoutsen
9b7b39055d Merge pull request #826 from andythigpen/feature/is-state-attr
Add is_state_attr method.
2015-12-31 14:17:59 -08:00
Andrew Thigpen
11f32d0500 Add is_state_attr method.
Returns True if the entity exists and has an attribute with the given
name and value.
2015-12-31 14:58:18 -06:00
Philip Lundrigan
326e26fbeb Merge pull request #825 from philipbl/locative
Update Locative component
2015-12-31 13:10:11 -07:00
Philip Lundrigan
394c87c40b Remove unnecessary condition in write_text 2015-12-31 13:05:24 -07:00
Philip Lundrigan
ce152e9c94 Simplify logic 2015-12-31 12:39:36 -07:00
pavoni
9e0946b207 Turn off polling for sensor too! 2015-12-31 19:15:21 +00:00
Paulus Schoutsen
da77edf8fc Merge pull request #818 from balloob/update-wemo-library
Improve wemo subscription error handling.
2015-12-31 11:06:43 -08:00
Paulus Schoutsen
6cb991d583 Merge pull request #814 from Mosibi/fix_mpd_title_keyerror
Fix KeyError on 'title' when title is empty
2015-12-31 11:04:30 -08:00
pavoni
f8e5df237b Remove '#'' from requirements 2015-12-31 18:58:12 +00:00
Philip Lundrigan
5d953061e8 Remove unnecessary error checking 2015-12-31 11:56:27 -07:00
Philip Lundrigan
1bcca8cba1 Fix problem with test 2015-12-31 11:56:27 -07:00
Philip Lundrigan
bdb6182921 Changes to locative based on tests 2015-12-31 11:56:27 -07:00
Philip Lundrigan
55d1ad94ef Add tests for Locative 2015-12-31 11:56:27 -07:00
pavoni
6773c35760 Bump pywemo version 2015-12-31 18:47:12 +00:00
pavoni
5f89b34831 Bump pyvera version 2015-12-31 16:09:05 +00:00
pavoni
90ae5c6646 Add missed import, fix style error. 2015-12-31 12:25:24 +00:00
pavoni
a8bb75d070 Update sensor with subscription code, change to use pyvera library 2015-12-31 12:16:03 +00:00
pavoni
d82859b6ea Turn off poll 2015-12-31 10:57:54 +00:00
Philip Lundrigan
7d41ce4e46 Switch from json messages to plain text messages 2015-12-30 22:43:32 -07:00
Philip Lundrigan
c23375a18b Add case for test message 2015-12-30 22:42:11 -07:00
MartinHjelmare
4c4e5d5f47 Fix to remove old unused variables. 2015-12-31 06:19:47 +01:00
MartinHjelmare
6ff24ed047 Merge branch 'dev' into mysensors-component-switch
Conflicts:
	homeassistant/components/sensor/__init__.py
	homeassistant/components/switch/__init__.py
2015-12-31 06:05:06 +01:00
MartinHjelmare
69ed6fe6e7 Add gateway wrapper, fix discovery and callbacks
* Add gateway wrapper by subclassing serial gateway.
* Fix platform setup with discovery service.
* Fix platform callback functions with callback factory.
2015-12-31 05:48:23 +01:00
Roy Hooper
4e2d75a8f4 fix style 2015-12-30 16:59:22 -05:00
pavoni
ae0dbbcfa5 Added support for event subscriptions 2015-12-30 19:44:02 +00:00
Philip Lundrigan
25e1432403 Fix style issues 2015-12-30 12:30:49 -07:00
Philip Lundrigan
adfcfad488 Update locative functionality 2015-12-30 12:26:03 -07:00
Roy Hooper
b0734e613f Add support for deCONZ (Raspbee-GW hue-like API) - Doesn't support the none transition type, so don't send it 2015-12-30 13:54:18 -05:00
Richard Arends
913c5ab47c identing error... sorry 2015-12-30 13:26:42 +01:00
Richard Arends
429904c437 Returning None when name and title are both not available
Removed trailing whitespaces
2015-12-30 13:00:34 +01:00
pavoni
41a36df801 Update pywemo version 2015-12-30 11:54:21 +00:00
Richard Arends
56a2ffca1d Changed if else statements. The following situations are handled now:
- name and title can be None
  - name can be None
  - title can be None
  - name and title can contain data
2015-12-29 22:10:09 +01:00
Richard Arends
6e2fb17f19 Fix KeyError on 'title' when title is empty 2015-12-29 17:52:05 +01:00
Paulus Schoutsen
586be7fad1 Prevent division by 0 xy->rgb color conversion 2015-12-29 00:09:38 -08:00
Paulus Schoutsen
d9b30d1421 Pep257 fixes for core. 2015-12-27 21:14:35 -08:00
happyleaves
e9059a3ed9 added test; addressed comments 2015-12-27 22:51:37 -05:00
Paulus Schoutsen
473d6b1d05 Fix console coloring for scripts 2015-12-27 19:19:29 -08:00
Paulus Schoutsen
7f17a50b4a Swap lint/requirements validation between Python versions 2015-12-27 18:04:38 -08:00
Paulus Schoutsen
c7183a14a5 Tweak lint script colors for travis 2015-12-27 18:03:23 -08:00
Paulus Schoutsen
c1eaf60461 VCR YR sensor test 2015-12-27 17:37:32 -08:00
Paulus Schoutsen
ca6b957839 Make test deps explicit 2015-12-27 16:57:16 -08:00
Paulus Schoutsen
680385df93 Hide some build log spam 2015-12-27 16:35:14 -08:00
Paulus Schoutsen
62f21c3ac6 colorize lint errors 2015-12-27 16:31:53 -08:00
Paulus Schoutsen
acfbbb3898 Update copyright 2015-12-27 16:31:52 -08:00
Paulus Schoutsen
4403fe941d Test config clean up 2015-12-27 16:31:52 -08:00
Paulus Schoutsen
244fde880e Convert MQTT event to lowercase 2015-12-27 16:31:52 -08:00
Greg Dowling
df99e4ef22 Merge pull request #794 from pavoni/wemo-subscription
Adds Wemo event support - so wemo devices update HA more quickly
2015-12-28 00:11:34 +00:00
pavoni
976d9f2d08 Fix style issue 2015-12-28 00:04:30 +00:00
pavoni
f5dd146676 Fix bug in shutdown 2015-12-27 23:56:18 +00:00
Paulus Schoutsen
7b00b19223 Merge pull request #807 from andythigpen/scene-fix
Fix issue with scene component when using YAML aliases.
2015-12-27 15:39:19 -08:00
pavoni
b114ba56ea Fix style issue 2015-12-27 23:31:48 +00:00
pavoni
0d32bd7a19 Simplify callback logging, add stop log 2015-12-27 23:26:35 +00:00
pavoni
4feef3dd0d Simplify update state call, shutdown properly. 2015-12-27 23:09:39 +00:00
Paulus Schoutsen
9c3b1b7a96 Fix sun import issue 2015-12-27 15:00:49 -08:00
Paulus Schoutsen
a09d731058 Merge pull request #803 from molobrakos/tellduslive2
added rudimentary support for telldus live
2015-12-27 15:00:01 -08:00
Erik
27e35d5f34 don't poll (turns out a request for state immediately a state turn on/off request will not return the newly updated state. import constants from tellive, not tellcore 2015-12-27 21:49:45 +01:00
Erik
c8fa6cc127 bug fix 2015-12-27 21:47:49 +01:00
Per Sandström
468087e6bc Merge pull request #806 from persandstrom/verisure-pypi
verisure module from pypi
2015-12-27 21:10:18 +01:00
Andrew Thigpen
d4b6a7343f Fix issue with scene component when using YAML aliases.
YAML aliases/anchors can make repetitive configuration sections easier
to deal with.  However when dealing with dictionaries, care needs to be
taken to not modify the original anchor since PyYAML utilizes a
reference when encountering an alias instead of a copy of the
dictionary.
2015-12-27 13:24:34 -06:00
Paulus Schoutsen
87c88078c8 Update sun to use elevation util 2015-12-27 11:22:10 -08:00
Paulus Schoutsen
8c1ebde1de Update requirements_all.txt 2015-12-27 11:10:14 -08:00
Paulus Schoutsen
ab5a3f9de3 Clean up YR sensor 2015-12-27 11:07:25 -08:00
Per Sandström
eb3da8cb03 verisure component from pypi 2015-12-27 20:07:09 +01:00
Paulus Schoutsen
9e1ecd7124 Fix flaky history test 2015-12-27 10:38:27 -08:00
Erik
efbaf47dc7 reference constants from tellive package 2015-12-27 18:49:11 +01:00
Erik
f10ecb2a8d remove obsolete comment 2015-12-27 18:42:10 +01:00
Erik
6e81391711 no percentage sign 2015-12-27 18:37:34 +01:00
Paulus Schoutsen
0d64f4a2d5 Merge pull request #804 from molobrakos/systemmon-icons
Systemmon icons
2015-12-27 08:54:14 -08:00
Erik
d63e5a60ae added rudimentary support for telldus live 2015-12-27 12:32:08 +01:00
Paulus Schoutsen
384f1344fd Merge branch 'pr/792' into dev 2015-12-26 17:48:29 -08:00
Paulus Schoutsen
add24915a3 ps - clean up sun automation tests 2015-12-26 17:48:20 -08:00
Paulus Schoutsen
089bbfc5cc Travis: Less verbose requirements_all.txt check 2015-12-26 17:19:53 -08:00
Paulus Schoutsen
1944b6a335 Merge pull request #796 from persandstrom/verisure-update
Verisure update
2015-12-26 08:17:08 -08:00
Paulus Schoutsen
b8cfe63fc6 Merge pull request #797 from molobrakos/eliq-icon
replace default icon
2015-12-26 08:15:01 -08:00
Paulus Schoutsen
9e091a2c92 Merge pull request #798 from R1chardTM/hue-random
Add random color effect for Philips Hue
2015-12-26 08:10:09 -08:00
Paulus Schoutsen
41bcdc3356 Merge pull request #793 from philipbl/rest_bug_fix
Fix bug in rest sensor
2015-12-25 15:00:52 -08:00
Philip Lundrigan
40486676df Simplify error handling 2015-12-25 14:34:30 -07:00
Erik
6f2078cda3 bugfix 2015-12-25 22:02:10 +01:00
Erik
b8d2f2ba37 moved into sensor types 2015-12-25 21:44:32 +01:00
Erik
03491fcc09 icons 2015-12-25 21:19:51 +01:00
Per Sandström
571073fe1f added docstring 2015-12-25 21:04:16 +01:00
Per Sandström
a577d5c5fa revert accidental change 2015-12-25 20:57:30 +01:00
Per Sandström
160814f425 log texts 2015-12-25 20:42:03 +01:00
Per Sandström
b83b36274a changed to python-verisure 0.4.1 2015-12-25 20:16:51 +01:00
Erik
f3db4306c2 added icon 2015-12-25 18:50:35 +01:00
richard
5c7fb5d7ae Fix styling issues 2015-12-24 21:35:36 -06:00
richard
0dfc1c4e7a Add functionality set random color Philips Hue 2015-12-24 21:10:27 -06:00
pavoni
3f151428b7 Update pywemo version, use wildcard filter 2015-12-24 09:35:02 +00:00
Philip Lundrigan
2606e4d641 Simplify if statement 2015-12-24 00:38:49 -07:00
MartinHjelmare
be25ea4f09 Fix avoid event bus for updates 2015-12-24 02:14:58 +01:00
MartinHjelmare
77959341a3 Merge branch 'dev' into mysensors-component-switch
Conflicts:
	homeassistant/components/sensor/mysensors.py
	requirements_all.txt
2015-12-23 23:45:32 +01:00
MartinHjelmare
9f54bcc21b Fix comments for pull request
* Fix cleaner user config.
* Remove bad disabling of linting.
* Extract default mysensors version into constant.
* Clean up selection of mysensors.CONST from version.
* Update mysensors update decorator to add devices and update values
	in one go.
* Fix persistence update.
* Clean up setup of ports.
* Setup of mysensors platforms from main mysensors component.
* Clean up v_types selection in mysensors sensor platform.
* Fix s_types and v_types selection version dependency in platforms.
2015-12-23 23:20:39 +01:00
pavoni
6d236b8169 Force state update 2015-12-23 18:03:40 +00:00
pavoni
09b894a4aa Fix style issues 2015-12-23 15:57:51 +00:00
pavoni
1e2b5e6991 Add support for subscriptions 2015-12-23 15:46:18 +00:00
Philip Lundrigan
496ec4bcca Fix bug in rest sensor 2015-12-23 03:37:03 -07:00
Paulus Schoutsen
3e51d0b539 Merge pull request #791 from andythigpen/logger-fix
Set the root logger to lowest level in logger component.
2015-12-22 23:13:45 -08:00
Andrew Thigpen
c31a291a9c Set the root logger to lowest level in logger component.
In combination with resetting the log level on the handlers, this
allows messages lower than the default INFO to be logged when using
the logger component.
2015-12-23 00:57:41 -06:00
Paulus Schoutsen
96938bb33c Merge pull request #786 from philipbl/locative
Rename geofancy to locative (fix #761)
2015-12-22 19:02:08 -08:00
Paulus Schoutsen
e7b1682a4e Merge branch 'pr/780' into dev 2015-12-22 18:57:42 -08:00
Paulus Schoutsen
d191635fd8 Merge branch 'yr_no' into dev 2015-12-22 18:53:27 -08:00
Paulus Schoutsen
bdd945c1c4 Set default log level to INFO 2015-12-22 18:39:46 -08:00
Paulus Schoutsen
6ccf63dae2 Merge pull request #790 from andythigpen/logger-fix
Reset log handlers to lowest level.
2015-12-22 18:38:58 -08:00
Paulus Schoutsen
987282da78 Logbook entry events are now lower case 2015-12-22 18:35:05 -08:00
Fabian Affolter
56186232f3 Enable logging-too-many-args 2015-12-22 22:33:20 +01:00
Fabian Affolter
fb2da6be9a Remove space 2015-12-22 22:02:55 +01:00
Fabian Affolter
8796187389 Equalize log messages 2015-12-22 22:02:55 +01:00
Andrew Thigpen
edff53609f Reset log handlers to lowest level.
This is necessary to enable logging lower than INFO for the error log file.
2015-12-22 13:50:59 -06:00
Paulus Schoutsen
dbc91c1d48 Merge pull request #789 from balloob/release-101
Release 0.10.1
2015-12-22 09:05:55 -08:00
Paulus Schoutsen
0607c8f4b4 Version bump to 0.10.1 2015-12-22 08:59:32 -08:00
Paulus Schoutsen
7ccb6b960c Merge branch 'release-101' into dev 2015-12-22 02:20:25 -08:00
Paulus Schoutsen
561a78bef3 Fix EntityComponent deadlock 2015-12-22 02:19:55 -08:00
Paulus Schoutsen
9876a2a081 Fix Alexa bug if no value for slots 2015-12-22 02:08:46 -08:00
happyleaves
fba5becd90 warn->warning 2015-12-21 20:18:00 -05:00
happyleaves
d5179b4bdc add statecmd to command_switch 2015-12-21 19:49:39 -05:00
Philip Lundrigan
ff8f22854c Add test 2015-12-21 16:28:26 -07:00
Philip Lundrigan
110d721c76 Add tests 2015-12-21 16:09:51 -07:00
Philip Lundrigan
8c010c8df4 Add ability to use sun as condition in automation 2015-12-21 16:09:27 -07:00
Philip Lundrigan
0ac1759395 Rename geofancy to locative (fix #761) 2015-12-21 11:05:20 -07:00
Paulus Schoutsen
07fb4ff243 Revert last fix. Will fix better. 2015-12-21 09:44:17 -08:00
Paulus Schoutsen
2650d235ea Fix: EntityComponent deadlock when adding new devices during update state 2015-12-21 08:56:27 -08:00
Daniel
9e89197284 small fix in yr sensor name 2015-12-21 10:42:42 +01:00
Daniel
8159d36114 small fix in yr sensor name 2015-12-21 10:36:25 +01:00
Daniel
cf4f4ce8c7 changed to use requestes in stead of urllib for yr sensor 2015-12-21 10:33:19 +01:00
Daniel
9a1883bb49 changed to use requestes in stead of urllib for yr sensor 2015-12-21 10:29:12 +01:00
Daniel
8e16a443e5 Added yr sensor to requirements_all 2015-12-21 10:24:22 +01:00
Paulus Schoutsen
35411cd57e Version bump to 0.11.dev0 2015-12-20 15:32:51 -08:00
MartinHjelmare
845926236e Add config sample and fix requirements_all 2015-12-18 03:58:21 +01:00
MartinHjelmare
eae07c070a Merge branch 'dev' into mysensors-component-switch 2015-12-18 03:39:41 +01:00
MartinHjelmare
659226886f Update .coveragerc and requirements 2015-12-18 03:37:49 +01:00
MartinHjelmare
1e52d5c7f2 Add S_TYPES to platform type and fix persistence
* Add S_TYPES to platform type.
* Fix persistence update on startup.
* Clean up code.
2015-12-09 04:43:18 +01:00
MartinHjelmare
9463c84603 Clean up 2015-12-08 02:47:15 +01:00
MartinHjelmare
7cc707f1ce Fix docstrings to conform to pep 2015-12-08 01:03:07 +01:00
MartinHjelmare
59524c7933 Add multiple gateways
* Add support for multiple serial gateways.
* Fix serialization of python objects by adding dict representation of
    classes.
* Add support for showing more than one child value type per entity.
    The entity state is always only one value type. This is defined by
    the platform value types. Value types that are not defined as the
    platform value type are shown as state_attributes.
* Add more unit of measurement types.
* Clean up code.
2015-12-06 00:29:03 +01:00
MartinHjelmare
1d141566bd Merge 'dev' into mysensors-component-switch 2015-12-05 23:55:36 +01:00
Daniel Hoyer Iversen
ac41f3028c Refactor yr sensor 2015-12-04 15:10:26 +01:00
Daniel Hoyer Iversen
750ca79ac0 Refactor yr sensor 2015-12-04 15:05:23 +01:00
Daniel Hoyer Iversen
71bf707bcf fix tests in yr sensor 2015-12-02 13:32:52 +01:00
Daniel Hoyer Iversen
31f1e1d7a4 added comment for yr sensor 2015-12-02 13:04:23 +01:00
Daniel Høyer Iversen
361ab0f92b Update yr.py 2015-12-01 20:19:59 +01:00
Daniel Høyer Iversen
e68a8f9c0f Update yr.py 2015-12-01 20:16:04 +01:00
Daniel Høyer Iversen
15770ff90f Update yr.py 2015-12-01 20:12:07 +01:00
Daniel Hoyer Iversen
618ebfe43c try to fix requirements for yr sensor 2015-12-01 13:40:26 +01:00
Daniel Hoyer Iversen
ff15fea9f8 Added units to sensor yr 2015-12-01 13:31:55 +01:00
Daniel Hoyer Iversen
2872c89f0c Fix in yr sensor 2015-12-01 13:24:32 +01:00
Daniel Hoyer Iversen
2dc9bc98f7 Tests for yr sensor 2015-12-01 13:24:03 +01:00
Daniel Hoyer Iversen
72d7e6e9dd Added requirements to yr sensor 2015-12-01 12:57:08 +01:00
Daniel Hoyer Iversen
f912daf4b2 Updated yr sensor 2015-12-01 12:46:08 +01:00
Daniel Hoyer Iversen
d9fc2a8bf6 initial version of yr.no weather component 2015-11-30 17:00:28 +01:00
MartinHjelmare
45fe37a301 Add mysensors component and switch platform
* Add a general mysensors component. This sets up the serial comm
        with the gateway through pymysensors. The component also
        contains a decorator function for the callback function of
        mysensors platforms. Mysensors platforms should create a
        function that listens for the node update event fired by the
        mysensors component. This function should call another
        function, that uses the decorator, and returns a dict. The dict
        should contain a list of which mysensors V_TYPE values the
        platform handles, the platfrom class and the add_devices
        function (from setup_platform).
    * Change existing mysensors sensor platform to depend on the new
        mysensors component.
    * Add a mysensors switch platform. The switch platform takes
        advantage of new functionality from the the fork of pymysensors
        https://github.com/MartinHjelmare/pymysensors, that enables the
        gateway to send commands to change node child values.
    * Change const and is_metric to global constants, in the mysensors
        component and import const depending on the mysensors version
        used.
    * Change variables devices and gateway to global variables.
    * Add some debug logging at INFO log level.
2015-11-06 01:58:41 +01:00
Malte Deiseroth
ef76047ba2 new try to add everythin from the stylecheckers 2015-09-22 13:27:08 +02:00
Malte Deiseroth
d475e5362b respect flake8 errors 2015-09-22 12:53:44 +02:00
Malte Deiseroth
3027b4a5a8 respect pylint suggestions 2015-09-22 12:32:45 +02:00
Malte Deiseroth
3829abbd2d updated with remote 2015-09-22 12:18:00 +02:00
Malte Deiseroth
e6e3b37a62 merged with current dev 2015-09-22 12:11:57 +02:00
Malte Deiseroth
ce501ae627 Improved onewire configuration 2015-09-16 14:17:41 +02:00
deisi
b6f954e082 Changed handling of config file
I tried to implement your suggesteions for the default handlig of the device names. I think this way, everything you wanted is in.
2015-09-16 10:18:11 +02:00
deisi
b368388714 Update .gitignore 2015-09-16 08:49:12 +02:00
Malte Deiseroth
8842e4e94f gitignore ready for emacs 2015-09-16 08:34:08 +02:00
Malte Deiseroth
0ed608abff little bug 2015-04-07 21:10:16 +02:00
Malte Deiseroth
afcf3eaac3 - add ds18S20 1-Wire sensor support
- gitignore emacs backup files
2015-03-29 14:48:17 +02:00
283 changed files with 20895 additions and 6223 deletions

View File

@@ -5,9 +5,21 @@ omit =
homeassistant/__main__.py
# 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
homeassistant/components/isy994.py
homeassistant/components/*/isy994.py
@@ -15,6 +27,10 @@ omit =
homeassistant/components/*/modbus.py
homeassistant/components/*/tellstick.py
homeassistant/components/tellduslive.py
homeassistant/components/*/tellduslive.py
homeassistant/components/*/vera.py
homeassistant/components/ecobee.py
@@ -26,12 +42,27 @@ omit =
homeassistant/components/wink.py
homeassistant/components/*/wink.py
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/mysensors.py
homeassistant/components/*/mysensors.py
homeassistant/components/nest.py
homeassistant/components/*/nest.py
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
@@ -41,12 +72,10 @@ omit =
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/geofancy.py
homeassistant/components/device_tracker/icloud.py
homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py
homeassistant/components/device_tracker/owntracks.py
homeassistant/components/device_tracker/snmp.py
homeassistant/components/device_tracker/thomson.py
homeassistant/components/device_tracker/tomato.py
@@ -54,12 +83,13 @@ 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/influxdb.py
homeassistant/components/keyboard.py
homeassistant/components/light/blinksticklight.py
homeassistant/components/light/hue.py
homeassistant/components/light/hyperion.py
homeassistant/components/light/lifx.py
homeassistant/components/light/limitlessled.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py
@@ -68,17 +98,23 @@ 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/sensor/arest.py
homeassistant/components/sensor/bitcoin.py
@@ -89,11 +125,12 @@ omit =
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/mysensors.py
homeassistant/components/sensor/netatmo.py
homeassistant/components/sensor/onewire.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/rpi_gpio.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
@@ -108,16 +145,14 @@ omit =
homeassistant/components/switch/mystrom.py
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_gpio.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wemo.py
homeassistant/components/thermostat/heatmiser.py
homeassistant/components/thermostat/homematic.py
homeassistant/components/thermostat/honeywell.py
homeassistant/components/thermostat/nest.py
homeassistant/components/thermostat/proliphix.py
homeassistant/components/thermostat/radiotherm.py
[report]
# Regexes for lines to exclude from consideration
exclude_lines =

11
.gitignore vendored
View File

@@ -41,6 +41,7 @@ Icon
dist
build
eggs
.eggs
parts
bin
var
@@ -68,6 +69,16 @@ nosetests.xml
.python-version
# emacs auto backups
*~
*#
*.orig
# venv stuff
pyvenv.cfg
pip-selfcheck.json
venv
# vimmy stuff
*.swp
*.swo

View File

@@ -8,9 +8,7 @@ python:
- 3.4
- 3.5
install:
# Validate requirements_all.txt on Python 3.5
- if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then python3 setup.py develop; script/gen_requirements_all.py validate; fi
- script/bootstrap_server
- "true"
script:
- script/cibuild
matrix:

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013 Paulus Schoutsen
Copyright (c) 2016 Paulus Schoutsen
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@@ -29,9 +29,13 @@ import time
import logging
from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_ON, STATE_OFF
import homeassistant.loader as loader
from homeassistant.helpers import validate_config
from homeassistant.helpers.event_decorators import \
track_state_change, track_time_change
from homeassistant.helpers.service import service
import homeassistant.components as core
from homeassistant.components import device_tracker
from homeassistant.components import light
# The domain of your component. Should be equal to the name of your component
DOMAIN = "example"
@@ -39,11 +43,14 @@ DOMAIN = "example"
# List of component names (string) your component depends upon
# We depend on group because group will be loaded after all the components that
# initialize devices have been setup.
DEPENDENCIES = ['group']
DEPENDENCIES = ['group', 'device_tracker', 'light']
# Configuration key for the entity id we are targetting
CONF_TARGET = 'target'
# Variable for storing configuration parameters
TARGET_ID = None
# Name of the service that we expose
SERVICE_FLASH = 'flash'
@@ -53,84 +60,89 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Setup example component. """
global TARGET_ID
# Validate that all required config options are given
if not validate_config(config, {DOMAIN: [CONF_TARGET]}, _LOGGER):
return False
target_id = config[DOMAIN][CONF_TARGET]
TARGET_ID = config[DOMAIN][CONF_TARGET]
# Validate that the target entity id exists
if hass.states.get(target_id) is None:
_LOGGER.error("Target entity id %s does not exist", target_id)
if hass.states.get(TARGET_ID) is None:
_LOGGER.error("Target entity id %s does not exist",
TARGET_ID)
# Tell the bootstrapper that we failed to initialize
# Tell the bootstrapper that we failed to initialize and clear the
# stored target id so our functions don't run.
TARGET_ID = None
return False
# We will use the component helper methods to check the states.
device_tracker = loader.get_component('device_tracker')
light = loader.get_component('light')
def track_devices(entity_id, old_state, new_state):
""" Called when the group.all devices change state. """
# If anyone comes home and the core is not on, turn it on.
if new_state.state == STATE_HOME and not core.is_on(hass, target_id):
core.turn_on(hass, target_id)
# If all people leave the house and the core is on, turn it off
elif new_state.state == STATE_NOT_HOME and core.is_on(hass, target_id):
core.turn_off(hass, target_id)
# Register our track_devices method to receive state changes of the
# all tracked devices group.
hass.states.track_change(
device_tracker.ENTITY_ID_ALL_DEVICES, track_devices)
def wake_up(now):
""" Turn it on in the morning if there are people home and
it is not already on. """
if device_tracker.is_on(hass) and not core.is_on(hass, target_id):
_LOGGER.info('People home at 7AM, turning it on')
core.turn_on(hass, target_id)
# Register our wake_up service to be called at 7AM in the morning
hass.track_time_change(wake_up, hour=7, minute=0, second=0)
def all_lights_off(entity_id, old_state, new_state):
""" If all lights turn off, turn off. """
if core.is_on(hass, target_id):
_LOGGER.info('All lights have been turned off, turning it off')
core.turn_off(hass, target_id)
# Register our all_lights_off method to be called when all lights turn off
hass.states.track_change(
light.ENTITY_ID_ALL_LIGHTS, all_lights_off, STATE_ON, STATE_OFF)
def flash_service(call):
""" Service that will turn the target off for 10 seconds
if on and vice versa. """
if core.is_on(hass, target_id):
core.turn_off(hass, target_id)
time.sleep(10)
core.turn_on(hass, target_id)
else:
core.turn_on(hass, target_id)
time.sleep(10)
core.turn_off(hass, target_id)
# Register our service with HASS.
hass.services.register(DOMAIN, SERVICE_FLASH, flash_service)
# Tells the bootstrapper that the component was successfully initialized
# Tell the bootstrapper that we initialized successfully
return True
@track_state_change(device_tracker.ENTITY_ID_ALL_DEVICES)
def track_devices(hass, entity_id, old_state, new_state):
""" Called when the group.all devices change state. """
# If the target id is not set, return
if not TARGET_ID:
return
# If anyone comes home and the entity is not on, turn it on.
if new_state.state == STATE_HOME and not core.is_on(hass, TARGET_ID):
core.turn_on(hass, TARGET_ID)
# If all people leave the house and the entity is on, turn it off
elif new_state.state == STATE_NOT_HOME and core.is_on(hass, TARGET_ID):
core.turn_off(hass, TARGET_ID)
@track_time_change(hour=7, minute=0, second=0)
def wake_up(hass, now):
"""
Turn it on in the morning (7 AM) if there are people home and
it is not already on.
"""
if not TARGET_ID:
return
if device_tracker.is_on(hass) and not core.is_on(hass, TARGET_ID):
_LOGGER.info('People home at 7AM, turning it on')
core.turn_on(hass, TARGET_ID)
@track_state_change(light.ENTITY_ID_ALL_LIGHTS, STATE_ON, STATE_OFF)
def all_lights_off(hass, entity_id, old_state, new_state):
""" If all lights turn off, turn off. """
if not TARGET_ID:
return
if core.is_on(hass, TARGET_ID):
_LOGGER.info('All lights have been turned off, turning it off')
core.turn_off(hass, TARGET_ID)
@service(DOMAIN, SERVICE_FLASH)
def flash_service(hass, call):
"""
Service that will turn the target off for 10 seconds if on and vice versa.
"""
if not TARGET_ID:
return
if core.is_on(hass, TARGET_ID):
core.turn_off(hass, TARGET_ID)
time.sleep(10)
core.turn_on(hass, TARGET_ID)
else:
core.turn_on(hass, TARGET_ID)
time.sleep(10)
core.turn_off(hass, TARGET_ID)

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())

View File

@@ -24,6 +24,7 @@ import homeassistant.config as config_util
import homeassistant.loader as loader
import homeassistant.components as core_components
import homeassistant.components.group as group
from homeassistant.helpers import event_decorators, service
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
__version__, EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE,
@@ -199,6 +200,10 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
_LOGGER.info('Home Assistant core initialized')
# give event decorators access to HASS
event_decorators.HASS = hass
service.HASS = hass
# Setup the components
for domain in loader.load_order_components(components):
_setup_component(hass, domain, config)
@@ -223,7 +228,7 @@ def from_config_file(config_path, hass=None, verbose=False, daemon=False,
enable_logging(hass, verbose, daemon, log_rotate_days)
config_dict = config_util.load_config_file(config_path)
config_dict = config_util.load_yaml_config_file(config_path)
return from_config_dict(config_dict, hass, enable_log=False,
skip_pip=skip_pip)
@@ -275,7 +280,7 @@ def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
datefmt='%y-%m-%d %H:%M:%S'))
logger = logging.getLogger('')
logger.addHandler(err_handler)
logger.setLevel(logging.NOTSET) # this sets the minimum log level
logger.setLevel(logging.INFO)
else:
_LOGGER.error(

View File

@@ -16,11 +16,11 @@ import itertools as it
import logging
import homeassistant.core as ha
import homeassistant.util as util
from homeassistant.helpers import extract_entity_ids
from homeassistant.helpers.entity import split_entity_id
from homeassistant.helpers.service import extract_entity_ids
from homeassistant.loader import get_component
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE)
_LOGGER = logging.getLogger(__name__)
@@ -36,7 +36,7 @@ def is_on(hass, entity_id=None):
entity_ids = hass.states.entity_ids()
for entity_id in entity_ids:
domain = util.split_entity_id(entity_id)[0]
domain = split_entity_id(entity_id)[0]
module = get_component(domain)
@@ -68,6 +68,14 @@ def turn_off(hass, entity_id=None, **service_data):
hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data)
def toggle(hass, entity_id=None, **service_data):
""" Toggles specified entity. """
if entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id
hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
def setup(hass, config):
""" Setup general services related to homeassistant. """
@@ -84,18 +92,27 @@ def setup(hass, config):
# Group entity_ids by domain. groupby requires sorted data.
by_domain = it.groupby(sorted(entity_ids),
lambda item: util.split_entity_id(item)[0])
lambda item: split_entity_id(item)[0])
for domain, ent_ids in by_domain:
# We want to block for all calls and only return when all calls
# have been processed. If a service does not exist it causes a 10
# second delay while we're blocking waiting for a response.
# But services can be registered on other HA instances that are
# listening to the bus too. So as a in between solution, we'll
# block only if the service is defined in the current HA instance.
blocking = hass.services.has_service(domain, service.service)
# Create a new dict for this call
data = dict(service.data)
# ent_ids is a generator, convert it to a list.
data[ATTR_ENTITY_ID] = list(ent_ids)
hass.services.call(domain, service.service, data, True)
hass.services.call(domain, service.service, data, blocking)
hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service)
hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)
hass.services.register(ha.DOMAIN, SERVICE_TOGGLE, handle_turn_service)
return True

View File

@@ -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'))

View File

@@ -0,0 +1,117 @@
"""
homeassistant.components.alarm_control_panel.alarmdotcom
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Interfaces with Verisure alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
"""
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import (
STATE_UNKNOWN,
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/Xorso/pyalarmdotcom'
'/archive/0.0.7.zip'
'#pyalarmdotcom==0.0.7']
DEFAULT_NAME = 'Alarm.com'
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup an Alarm.com control panel. """
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
if username is None or password is None:
_LOGGER.error('Must specify username and password!')
return False
add_devices([AlarmDotCom(hass,
config.get('name', DEFAULT_NAME),
config.get('code'),
username,
password)])
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class AlarmDotCom(alarm.AlarmControlPanel):
""" Represents a Alarm.com status. """
def __init__(self, hass, name, code, username, password):
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
self._alarm = Alarmdotcom(username, password, timeout=10)
self._hass = hass
self._name = name
self._code = str(code) if code else None
self._username = username
self._password = password
@property
def should_poll(self):
""" No polling needed. """
return True
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def code_format(self):
""" One or more characters if code is defined. """
return None if self._code is None else '.+'
@property
def state(self):
""" Returns the state of the device. """
if self._alarm.state == 'Disarmed':
return STATE_ALARM_DISARMED
elif self._alarm.state == 'Armed Stay':
return STATE_ALARM_ARMED_HOME
elif self._alarm.state == 'Armed Away':
return STATE_ALARM_ARMED_AWAY
else:
return STATE_UNKNOWN
def alarm_disarm(self, code=None):
""" Send disarm command. """
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.disarm()
def alarm_arm_home(self, code=None):
""" Send arm home command. """
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.arm_stay()
def alarm_arm_away(self, code=None):
""" Send arm away command. """
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.arm_away()
def _validate_code(self, code, state):
""" Validate given code. """
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Wrong code entered for %s', state)
return check

View File

@@ -68,7 +68,8 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property
def state(self):
""" Returns the state of the device. """
if self._state in (STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY) and \
if self._state in (STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY) and \
self._pending_time and self._state_ts + self._pending_time > \
dt_util.utcnow():
return STATE_ALARM_PENDING

View File

@@ -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()

View File

@@ -29,7 +29,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
alarms.extend([
VerisureAlarm(value)
for value in verisure.get_alarm_status().values()
for value in verisure.ALARM_STATUS.values()
if verisure.SHOW_ALARM
])
@@ -42,7 +42,6 @@ class VerisureAlarm(alarm.AlarmControlPanel):
def __init__(self, alarm_status):
self._id = alarm_status.id
self._device = verisure.MY_PAGES.DEVICE_ALARM
self._state = STATE_UNKNOWN
@property
@@ -58,40 +57,40 @@ class VerisureAlarm(alarm.AlarmControlPanel):
@property
def code_format(self):
""" Four digit code required. """
return '^\\d{4}$'
return '^\\d{%s}$' % verisure.CODE_DIGITS
def update(self):
""" Update alarm status """
verisure.update()
verisure.update_alarm()
if verisure.STATUS[self._device][self._id].status == 'unarmed':
if verisure.ALARM_STATUS[self._id].status == 'unarmed':
self._state = STATE_ALARM_DISARMED
elif verisure.STATUS[self._device][self._id].status == 'armedhome':
elif verisure.ALARM_STATUS[self._id].status == 'armedhome':
self._state = STATE_ALARM_ARMED_HOME
elif verisure.STATUS[self._device][self._id].status == 'armedaway':
elif verisure.ALARM_STATUS[self._id].status == 'armed':
self._state = STATE_ALARM_ARMED_AWAY
elif verisure.STATUS[self._device][self._id].status != 'pending':
elif verisure.ALARM_STATUS[self._id].status != 'pending':
_LOGGER.error(
'Unknown alarm state %s',
verisure.STATUS[self._device][self._id].status)
verisure.ALARM_STATUS[self._id].status)
def alarm_disarm(self, code=None):
""" Send disarm command. """
verisure.MY_PAGES.set_alarm_status(
code,
verisure.MY_PAGES.ALARM_DISARMED)
_LOGGER.warning('disarming')
verisure.MY_PAGES.alarm.set(code, 'DISARMED')
_LOGGER.info('verisure alarm disarming')
verisure.MY_PAGES.alarm.wait_while_pending()
verisure.update_alarm()
def alarm_arm_home(self, code=None):
""" Send arm home command. """
verisure.MY_PAGES.set_alarm_status(
code,
verisure.MY_PAGES.ALARM_ARMED_HOME)
_LOGGER.warning('arming home')
verisure.MY_PAGES.alarm.set(code, 'ARMED_HOME')
_LOGGER.info('verisure alarm arming home')
verisure.MY_PAGES.alarm.wait_while_pending()
verisure.update_alarm()
def alarm_arm_away(self, code=None):
""" Send arm away command. """
verisure.MY_PAGES.set_alarm_status(
code,
verisure.MY_PAGES.ALARM_ARMED_AWAY)
_LOGGER.warning('arming away')
verisure.MY_PAGES.alarm.set(code, 'ARMED_AWAY')
_LOGGER.info('verisure alarm arming away')
verisure.MY_PAGES.alarm.wait_while_pending()
verisure.update_alarm()

View File

@@ -11,6 +11,7 @@ import logging
from homeassistant.const import HTTP_OK, HTTP_UNPROCESSABLE_ENTITY
from homeassistant.util import template
from homeassistant.helpers.service import call_from_config
DOMAIN = 'alexa'
DEPENDENCIES = ['http']
@@ -23,6 +24,7 @@ API_ENDPOINT = '/api/alexa'
CONF_INTENTS = 'intents'
CONF_CARD = 'card'
CONF_SPEECH = 'speech'
CONF_ACTION = 'action'
def setup(hass, config):
@@ -80,6 +82,7 @@ def _handle_alexa(handler, path_match, data):
speech = config.get(CONF_SPEECH)
card = config.get(CONF_CARD)
action = config.get(CONF_ACTION)
# pylint: disable=unsubscriptable-object
if speech is not None:
@@ -89,6 +92,9 @@ def _handle_alexa(handler, path_match, data):
response.add_card(CardType[card['type']], card['title'],
card['content'])
if action is not None:
call_from_config(handler.server.hass, action, True)
handler.write_json(response.as_dict())
@@ -116,7 +122,7 @@ class AlexaResponse(object):
self.should_end_session = True
if intent is not None and 'slots' in intent:
self.variables = {key: value['value'] for key, value
in intent['slots'].items()}
in intent['slots'].items() if 'value' in value}
else:
self.variables = {}

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()

View File

@@ -9,11 +9,6 @@ https://home-assistant.io/components/arduino/
"""
import logging
try:
from PyMata.pymata import PyMata
except ImportError:
PyMata = None
from homeassistant.helpers import validate_config
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP)
@@ -27,18 +22,12 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Setup the Arduino component. """
global PyMata # pylint: disable=invalid-name
if PyMata is None:
from PyMata.pymata import PyMata as PyMata_
PyMata = PyMata_
import serial
if not validate_config(config,
{DOMAIN: ['port']},
_LOGGER):
return False
import serial
global BOARD
try:
BOARD = ArduinoBoard(config[DOMAIN]['port'])
@@ -67,6 +56,7 @@ class ArduinoBoard(object):
""" Represents an Arduino board. """
def __init__(self, port):
from PyMata.pymata import PyMata
self._port = port
self._board = PyMata(self._port, verbose=False)

View File

@@ -9,9 +9,9 @@ https://home-assistant.io/components/automation/
import logging
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.util import split_entity_id
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
from homeassistant.const import CONF_PLATFORM
from homeassistant.components import logbook
from homeassistant.helpers.service import call_from_config
DOMAIN = 'automation'
@@ -19,8 +19,6 @@ DEPENDENCIES = ['group']
CONF_ALIAS = 'alias'
CONF_SERVICE = 'service'
CONF_SERVICE_ENTITY_ID = 'entity_id'
CONF_SERVICE_DATA = 'data'
CONF_CONDITION = 'condition'
CONF_ACTION = 'action'
@@ -96,22 +94,7 @@ def _get_action(hass, config, name):
_LOGGER.info('Executing %s', name)
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
domain, service = split_entity_id(config[CONF_SERVICE])
service_data = config.get(CONF_SERVICE_DATA, {})
if not isinstance(service_data, dict):
_LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA)
service_data = {}
if CONF_SERVICE_ENTITY_ID in config:
try:
service_data[ATTR_ENTITY_ID] = \
config[CONF_SERVICE_ENTITY_ID].split(",")
except AttributeError:
service_data[ATTR_ENTITY_ID] = \
config[CONF_SERVICE_ENTITY_ID]
hass.services.call(domain, service, service_data)
call_from_config(hass, config)
return action

View File

@@ -6,6 +6,7 @@ Offers numeric state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#numeric-state-trigger
"""
from functools import partial
import logging
from homeassistant.const import CONF_VALUE_TEMPLATE
@@ -20,6 +21,14 @@ CONF_ABOVE = "above"
_LOGGER = logging.getLogger(__name__)
def _renderer(hass, value_template, state):
"""Render state value."""
if value_template is None:
return state.state
return template.render(hass, value_template, {'state': state})
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
entity_id = config.get(CONF_ENTITY_ID)
@@ -38,12 +47,7 @@ def trigger(hass, config, action):
CONF_BELOW, CONF_ABOVE)
return False
if value_template is not None:
renderer = lambda value: template.render(hass,
value_template,
{'state': value})
else:
renderer = lambda value: value.state
renderer = partial(_renderer, hass, value_template)
# pylint: disable=unused-argument
def state_automation_listener(entity, from_s, to_s):
@@ -79,12 +83,7 @@ def if_action(hass, config):
CONF_BELOW, CONF_ABOVE)
return None
if value_template is not None:
renderer = lambda value: template.render(hass,
value_template,
{'state': value})
else:
renderer = lambda value: value.state
renderer = partial(_renderer, hass, value_template)
def if_numeric_state():
""" Test numeric state condition. """

View File

@@ -10,13 +10,17 @@ import logging
from datetime import timedelta
from homeassistant.components import sun
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.helpers.event import track_sunrise, track_sunset
import homeassistant.util.dt as dt_util
DEPENDENCIES = ['sun']
CONF_OFFSET = 'offset'
CONF_EVENT = 'event'
CONF_BEFORE = "before"
CONF_BEFORE_OFFSET = "before_offset"
CONF_AFTER = "after"
CONF_AFTER_OFFSET = "after_offset"
EVENT_SUNSET = 'sunset'
EVENT_SUNRISE = 'sunrise'
@@ -37,69 +41,108 @@ def trigger(hass, config, action):
_LOGGER.error("Invalid value for %s: %s", CONF_EVENT, event)
return False
if CONF_OFFSET in config:
raw_offset = config.get(CONF_OFFSET)
negative_offset = False
if raw_offset.startswith('-'):
negative_offset = True
raw_offset = raw_offset[1:]
try:
(hour, minute, second) = [int(x) for x in raw_offset.split(':')]
except ValueError:
_LOGGER.error('Could not parse offset %s', raw_offset)
return False
offset = timedelta(hours=hour, minutes=minute, seconds=second)
if negative_offset:
offset *= -1
else:
offset = timedelta(0)
offset = _parse_offset(config.get(CONF_OFFSET))
if offset is False:
return False
# Do something to call action
if event == EVENT_SUNRISE:
trigger_sunrise(hass, action, offset)
track_sunrise(hass, action, offset)
else:
trigger_sunset(hass, action, offset)
track_sunset(hass, action, offset)
return True
def trigger_sunrise(hass, action, offset):
""" Trigger action at next sun rise. """
def next_rise():
""" Returns next sunrise. """
next_time = sun.next_rising_utc(hass) + offset
def if_action(hass, config):
""" Wraps action method with sun based condition. """
before = config.get(CONF_BEFORE)
after = config.get(CONF_AFTER)
while next_time < dt_util.utcnow():
next_time = next_time + timedelta(days=1)
# Make sure required configuration keys are present
if before is None and after is None:
logging.getLogger(__name__).error(
"Missing if-condition configuration key %s or %s",
CONF_BEFORE, CONF_AFTER)
return None
return next_time
# Make sure configuration keys have the right value
if before not in (None, EVENT_SUNRISE, EVENT_SUNSET) or \
after not in (None, EVENT_SUNRISE, EVENT_SUNSET):
logging.getLogger(__name__).error(
"%s and %s can only be set to %s or %s",
CONF_BEFORE, CONF_AFTER, EVENT_SUNRISE, EVENT_SUNSET)
return None
def sunrise_automation_listener(now):
""" Called when it's time for action. """
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
action()
before_offset = _parse_offset(config.get(CONF_BEFORE_OFFSET))
after_offset = _parse_offset(config.get(CONF_AFTER_OFFSET))
if before_offset is False or after_offset is False:
return None
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
if before is None:
def before_func():
"""Return no point in time."""
return None
elif before == EVENT_SUNRISE:
def before_func():
"""Return time before sunrise."""
return sun.next_rising(hass) + before_offset
else:
def before_func():
"""Return time before sunset."""
return sun.next_setting(hass) + before_offset
if after is None:
def after_func():
"""Return no point in time."""
return None
elif after == EVENT_SUNRISE:
def after_func():
"""Return time after sunrise."""
return sun.next_rising(hass) + after_offset
else:
def after_func():
"""Return time after sunset."""
return sun.next_setting(hass) + after_offset
def time_if():
""" Validate time based if-condition """
now = dt_util.now()
before = before_func()
after = after_func()
if before is not None and now > now.replace(hour=before.hour,
minute=before.minute):
return False
if after is not None and now < now.replace(hour=after.hour,
minute=after.minute):
return False
return True
return time_if
def trigger_sunset(hass, action, offset):
""" Trigger action at next sun set. """
def next_set():
""" Returns next sunrise. """
next_time = sun.next_setting_utc(hass) + offset
def _parse_offset(raw_offset):
if raw_offset is None:
return timedelta(0)
while next_time < dt_util.utcnow():
next_time = next_time + timedelta(days=1)
negative_offset = False
if raw_offset.startswith('-'):
negative_offset = True
raw_offset = raw_offset[1:]
return next_time
try:
(hour, minute, second) = [int(x) for x in raw_offset.split(':')]
except ValueError:
_LOGGER.error('Could not parse offset %s', raw_offset)
return False
def sunset_automation_listener(now):
""" Called when it's time for action. """
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
action()
offset = timedelta(hours=hour, minutes=minute, seconds=second)
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
if negative_offset:
offset *= -1
return offset

View File

@@ -8,7 +8,6 @@ at https://home-assistant.io/components/automation/#time-trigger
"""
import logging
from homeassistant.util import convert
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_time_change
@@ -32,11 +31,11 @@ def trigger(hass, config, action):
_error_time(config[CONF_AFTER], CONF_AFTER)
return False
hours, minutes, seconds = after.hour, after.minute, after.second
elif (CONF_HOURS in config or CONF_MINUTES in config
or CONF_SECONDS in config):
hours = convert(config.get(CONF_HOURS), int)
minutes = convert(config.get(CONF_MINUTES), int)
seconds = convert(config.get(CONF_SECONDS), int)
elif (CONF_HOURS in config or CONF_MINUTES in config or
CONF_SECONDS in config):
hours = config.get(CONF_HOURS)
minutes = config.get(CONF_MINUTES)
seconds = config.get(CONF_SECONDS)
else:
_LOGGER.error('One of %s, %s, %s OR %s needs to be specified',
CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AFTER)
@@ -59,7 +58,7 @@ def if_action(hass, config):
weekday = config.get(CONF_WEEKDAY)
if before is None and after is None and weekday is None:
logging.getLogger(__name__).error(
_LOGGER.error(
"Missing if-condition configuration key %s, %s or %s",
CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY)
return None

View File

@@ -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]

View File

@@ -0,0 +1,84 @@
"""
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
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.sensor.command_sensor import CommandSensorData
from homeassistant.util import template
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "Binary Command Sensor"
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Add the Command Sensor. """
if config.get('command') is None:
_LOGGER.error('Missing required variable: "command"')
return False
data = CommandSensorData(config.get('command'))
add_devices([CommandBinarySensor(
hass,
data,
config.get('name', DEFAULT_NAME),
config.get('payload_on', DEFAULT_PAYLOAD_ON),
config.get('payload_off', DEFAULT_PAYLOAD_OFF),
config.get(CONF_VALUE_TEMPLATE)
)])
# pylint: disable=too-many-arguments
class CommandBinarySensor(BinarySensorDevice):
""" Represents a binary sensor that is returning
a value of a shell commands. """
def __init__(self, hass, data, name, payload_on,
payload_off, value_template):
self._hass = hass
self.data = data
self._name = name
self._state = False
self._payload_on = payload_on
self._payload_off = payload_off
self._value_template = value_template
self.update()
@property
def name(self):
""" The name of the sensor. """
return self._name
@property
def is_on(self):
""" True if the binary sensor is on. """
return self._state
def update(self):
""" Gets the latest data and updates the state. """
self.data.update()
value = self.data.value
if self._value_template is not None:
value = template.render_with_possible_json_value(
self._hass, self._value_template, value, False)
if value == self._payload_on:
self._state = True
elif value == self._payload_off:
self._state = False

View File

@@ -0,0 +1,56 @@
"""
homeassistant.components.binary_sensor.nest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Nest Thermostat Binary Sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.nest/
"""
import logging
import socket
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',
'hvac_alt_heat_x2_state',
'hvac_emer_heat_state',
'online']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup Nest binary sensors. """
logger = logging.getLogger(__name__)
try:
for structure in nest.NEST.structures:
for device in structure.devices:
for variable in config['monitored_conditions']:
if variable in BINARY_TYPES:
add_devices([NestBinarySensor(structure,
device,
variable)])
else:
logger.error('Nest sensor type: "%s" does not exist',
variable)
except socket.error:
logger.error(
"Connection error logging into the nest web service."
)
class NestBinarySensor(NestSensor, BinarySensorDevice):
""" Represents a Nest binary sensor. """
@property
def is_on(self):
""" True if the binary sensor is on. """
return bool(getattr(self.device, self.variable))

View File

@@ -6,12 +6,11 @@ The rest binary sensor will consume responses sent by an exposed REST API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rest/
"""
from datetime import timedelta
import logging
import requests
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.util import template, Throttle
from homeassistant.util import template
from homeassistant.components.sensor.rest import RestData
from homeassistant.components.binary_sensor import BinarySensorDevice
_LOGGER = logging.getLogger(__name__)
@@ -19,61 +18,33 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'REST Binary Sensor'
DEFAULT_METHOD = 'GET'
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the REST binary sensor. """
use_get = False
use_post = False
""" Setup REST binary sensors. """
resource = config.get('resource', None)
method = config.get('method', DEFAULT_METHOD)
payload = config.get('payload', None)
verify_ssl = config.get('verify_ssl', True)
if method == 'GET':
use_get = True
elif method == 'POST':
use_post = True
rest = RestData(method, resource, payload, verify_ssl)
rest.update()
try:
if use_get:
response = requests.get(resource, timeout=10, verify=verify_ssl)
elif use_post:
response = requests.post(resource, data=payload, timeout=10,
verify=verify_ssl)
if not response.ok:
_LOGGER.error('Response status is "%s"', response.status_code)
return False
except requests.exceptions.MissingSchema:
_LOGGER.error('Missing resource or schema in configuration. '
'Add http:// to your URL.')
return False
except requests.exceptions.ConnectionError:
_LOGGER.error('No route to resource/endpoint: %s',
resource)
if rest.data is None:
_LOGGER.error('Unable to fetch Rest data')
return False
if use_get:
rest = RestDataGet(resource, verify_ssl)
elif use_post:
rest = RestDataPost(resource, payload, verify_ssl)
add_devices([RestBinarySensor(hass,
rest,
config.get('name', DEFAULT_NAME),
config.get(CONF_VALUE_TEMPLATE))])
add_devices([RestBinarySensor(
hass, rest, config.get('name', DEFAULT_NAME),
config.get(CONF_VALUE_TEMPLATE))])
# pylint: disable=too-many-arguments
class RestBinarySensor(BinarySensorDevice):
""" Implements a REST binary sensor. """
""" A REST binary sensor. """
def __init__(self, hass, rest, name, value_template):
""" Initialize a REST binary sensor. """
self._hass = hass
self.rest = rest
self._name = name
@@ -83,63 +54,20 @@ class RestBinarySensor(BinarySensorDevice):
@property
def name(self):
""" The name of the binary sensor. """
""" Name of the binary sensor. """
return self._name
@property
def is_on(self):
""" True if the binary sensor is on. """
if self.rest.data is False:
""" Return if the binary sensor is on. """
if self.rest.data is None:
return False
else:
if self._value_template is not None:
self.rest.data = template.render_with_possible_json_value(
self._hass, self._value_template, self.rest.data, False)
return bool(int(self.rest.data))
if self._value_template is not None:
self.rest.data = template.render_with_possible_json_value(
self._hass, self._value_template, self.rest.data, False)
return bool(int(self.rest.data))
def update(self):
""" Gets the latest data from REST API and updates the state. """
""" Get the latest data from REST API and updates the state. """
self.rest.update()
# pylint: disable=too-few-public-methods
class RestDataGet(object):
""" Class for handling the data retrieval with GET method. """
def __init__(self, resource, verify_ssl):
self._resource = resource
self._verify_ssl = verify_ssl
self.data = False
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data from REST service with GET method. """
try:
response = requests.get(self._resource, timeout=10,
verify=self._verify_ssl)
self.data = response.text
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to resource/endpoint: %s", self._resource)
self.data = False
# pylint: disable=too-few-public-methods
class RestDataPost(object):
""" Class for handling the data retrieval with POST method. """
def __init__(self, resource, payload, verify_ssl):
self._resource = resource
self._payload = payload
self._verify_ssl = verify_ssl
self.data = False
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data from REST service with POST method. """
try:
response = requests.post(self._resource, data=self._payload,
timeout=10, verify=self._verify_ssl)
self.data = response.text
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to resource/endpoint: %s", self._resource)
self.data = False

View File

@@ -0,0 +1,73 @@
"""
homeassistant.components.binary_sensor.rpi_gpio
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure a binary sensor using RPi GPIO.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rpi_gpio/
"""
import logging
import homeassistant.components.rpi_gpio as rpi_gpio
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import (DEVICE_DEFAULT_NAME)
DEFAULT_PULL_MODE = "UP"
DEFAULT_BOUNCETIME = 50
DEFAULT_INVERT_LOGIC = False
DEPENDENCIES = ['rpi_gpio']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Raspberry PI GPIO devices. """
pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE)
bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME)
invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC)
binary_sensors = []
ports = config.get('ports')
for port_num, port_name in ports.items():
binary_sensors.append(RPiGPIOBinarySensor(
port_name, port_num, pull_mode, bouncetime, invert_logic))
add_devices(binary_sensors)
# pylint: disable=too-many-arguments, too-many-instance-attributes
class RPiGPIOBinarySensor(BinarySensorDevice):
""" Represents a binary sensor that uses Raspberry Pi GPIO. """
def __init__(self, name, port, pull_mode, bouncetime, invert_logic):
# pylint: disable=no-member
self._name = name or DEVICE_DEFAULT_NAME
self._port = port
self._pull_mode = pull_mode
self._bouncetime = bouncetime
self._invert_logic = invert_logic
rpi_gpio.setup_input(self._port, self._pull_mode)
self._state = rpi_gpio.read_input(self._port)
def read_gpio(port):
""" Reads state from GPIO. """
self._state = rpi_gpio.read_input(self._port)
self.update_ha_state()
rpi_gpio.edge_detect(self._port, read_gpio, self._bouncetime)
@property
def should_poll(self):
""" No polling needed. """
return False
@property
def name(self):
""" The name of the sensor. """
return self._name
@property
def is_on(self):
""" Returns the state of the entity. """
return self._state != self._invert_logic

View File

@@ -0,0 +1,29 @@
"""
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)
DEPENDENCIES = ["zigbee"]
def setup_platform(hass, config, add_entities, discovery_info=None):
""" Create and add an entity based on the configuration. """
add_entities([
ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))
])
class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice):
"""
Use multiple inheritance to turn a ZigBeeDigitalIn into a
BinarySensorDevice.
"""
pass

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()
})

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. """

View File

@@ -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

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):

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()

View File

@@ -11,7 +11,7 @@ the user has submitted configuration information.
"""
import logging
from homeassistant.helpers import generate_entity_id
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.const import EVENT_TIME_CHANGED
DOMAIN = "configurator"
@@ -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)

View File

@@ -21,6 +21,7 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
'binary_sensor',
'camera',
'device_tracker',
'garage_door',
'light',
'lock',
'media_player',
@@ -62,10 +63,16 @@ def setup(hass, config):
lights = sorted(hass.states.entity_ids('light'))
switches = sorted(hass.states.entity_ids('switch'))
media_players = sorted(hass.states.entity_ids('media_player'))
group.setup_group(hass, 'living room', [lights[2], lights[1], switches[0],
media_players[1]])
group.setup_group(hass, 'bedroom', [lights[0], switches[1],
media_players[0]])
group.Group(hass, 'living room', [
lights[2], lights[1], switches[0], media_players[1],
'scene.romantic_lights'])
group.Group(hass, 'bedroom', [lights[0], switches[1],
media_players[0]])
group.Group(hass, 'Rooms', [
'group.living_room', 'group.bedroom',
'scene.romantic_lights', 'rollershutter.kitchen_window',
'rollershutter.living_room_window',
], view=True)
# Setup scripts
bootstrap.setup_component(

View File

@@ -229,7 +229,7 @@ class DeviceTracker(object):
""" Initializes group for all tracked devices. """
entity_ids = (dev.entity_id for dev in self.devices.values()
if dev.track)
self.group = group.setup_group(
self.group = group.Group(
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
def update_stale(self, now):

View File

@@ -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

View File

@@ -58,8 +58,8 @@ class AsusWrtDeviceScanner(object):
def __init__(self, config):
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.username = str(config[CONF_USERNAME])
self.password = str(config[CONF_PASSWORD])
self.lock = threading.Lock()

View File

@@ -15,6 +15,8 @@ from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
from homeassistant.components.device_tracker import DOMAIN
REQUIREMENTS = ['fritzconnection==0.4.6']
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
@@ -55,16 +57,8 @@ class FritzBoxScanner(object):
self.password = ''
self.success_init = True
# Try to import the fritzconnection library
try:
# noinspection PyPackageRequirements,PyUnresolvedReferences
import fritzconnection as fc
except ImportError:
_LOGGER.exception("""Failed to import Python library
fritzconnection. Please run
<home-assistant>/setup to install it.""")
self.success_init = False
return
# pylint: disable=import-error
import fritzconnection as fc
# Check for user specific configuration
if CONF_HOST in config.keys():

View File

@@ -1,70 +0,0 @@
"""
homeassistant.components.device_tracker.geofancy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Geofancy platform for the device tracker.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.geofancy/
"""
from homeassistant.const import (
HTTP_UNPROCESSABLE_ENTITY, HTTP_INTERNAL_SERVER_ERROR)
DEPENDENCIES = ['http']
_SEE = 0
URL_API_GEOFANCY_ENDPOINT = "/api/geofancy"
def setup_scanner(hass, config, see):
""" Set up an endpoint for the Geofancy app. """
# Use a global variable to keep setup_scanner compact when using a callback
global _SEE
_SEE = see
# POST would be semantically better, but that currently does not work
# since Geofancy sends the data as key1=value1&key2=value2
# in the request body, while Home Assistant expects json there.
hass.http.register_path(
'GET', URL_API_GEOFANCY_ENDPOINT, _handle_get_api_geofancy)
return True
def _handle_get_api_geofancy(handler, path_match, data):
""" Geofancy message received. """
if not isinstance(data, dict):
handler.write_json_message(
"Error while parsing Geofancy message.",
HTTP_INTERNAL_SERVER_ERROR)
return
if 'latitude' not in data or 'longitude' not in data:
handler.write_json_message(
"Location not specified.",
HTTP_UNPROCESSABLE_ENTITY)
return
if 'device' not in data or 'id' not in data:
handler.write_json_message(
"Device id or location id not specified.",
HTTP_UNPROCESSABLE_ENTITY)
return
try:
gps_coords = (float(data['latitude']), float(data['longitude']))
except ValueError:
# If invalid latitude / longitude format
handler.write_json_message(
"Invalid latitude / longitude format.",
HTTP_UNPROCESSABLE_ENTITY)
return
# entity id's in Home Assistant must be alphanumerical
device_uuid = data['device']
device_entity_id = device_uuid.replace('-', '')
_SEE(dev_id=device_entity_id, gps=gps_coords, location_name=data['id'])
handler.write_json_message("Geofancy message processed")

View File

@@ -0,0 +1,104 @@
"""
homeassistant.components.device_tracker.locative
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Locative platform for the device tracker.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.locative/
"""
import logging
from functools import partial
from homeassistant.const import (
HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME)
from homeassistant.components.device_tracker import DOMAIN
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
URL_API_LOCATIVE_ENDPOINT = "/api/locative"
def setup_scanner(hass, config, see):
""" Set up an endpoint for the Locative app. """
# POST would be semantically better, but that currently does not work
# since Locative sends the data as key1=value1&key2=value2
# in the request body, while Home Assistant expects json there.
hass.http.register_path(
'GET', URL_API_LOCATIVE_ENDPOINT,
partial(_handle_get_api_locative, hass, see))
return True
def _handle_get_api_locative(hass, see, handler, path_match, data):
""" Locative message received. """
if not _check_data(handler, data):
return
device = data['device'].replace('-', '')
location_name = data['id'].lower()
direction = data['trigger']
if direction == 'enter':
see(dev_id=device, location_name=location_name)
handler.write_text("Setting location to {}".format(location_name))
elif direction == 'exit':
current_state = hass.states.get("{}.{}".format(DOMAIN, device))
if current_state is None or current_state.state == location_name:
see(dev_id=device, location_name=STATE_NOT_HOME)
handler.write_text("Setting location to not home")
else:
# Ignore the message if it is telling us to exit a zone that we
# aren't currently in. This occurs when a zone is entered before
# the previous zone was exited. The enter message will be sent
# first, then the exit message will be sent second.
handler.write_text(
'Ignoring exit from {} (already in {})'.format(
location_name, current_state))
elif direction == 'test':
# In the app, a test message can be sent. Just return something to
# the user to let them know that it works.
handler.write_text("Received test message.")
else:
handler.write_text(
"Received unidentified message: {}".format(direction),
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Received unidentified message from Locative: %s",
direction)
def _check_data(handler, data):
if 'latitude' not in data or 'longitude' not in data:
handler.write_text("Latitude and longitude not specified.",
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Latitude and longitude not specified.")
return False
if 'device' not in data:
handler.write_text("Device id not specified.",
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Device id not specified.")
return False
if 'id' not in data:
handler.write_text("Location id not specified.",
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Location id not specified.")
return False
if 'trigger' not in data:
handler.write_text("Trigger is not specified.",
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Trigger is not specified.")
return False
return True

View File

@@ -19,7 +19,7 @@ from homeassistant.components.device_tracker import DOMAIN
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pynetgear==0.3']
REQUIREMENTS = ['pynetgear==0.3.2']
def get_scanner(hass, config):

View File

@@ -8,16 +8,29 @@ https://home-assistant.io/components/device_tracker.owntracks/
"""
import json
import logging
import threading
from collections import defaultdict
import homeassistant.components.mqtt as mqtt
from homeassistant.const import STATE_HOME
DEPENDENCIES = ['mqtt']
REGIONS_ENTERED = defaultdict(list)
MOBILE_BEACONS_ACTIVE = defaultdict(list)
BEACON_DEV_ID = 'beacon'
LOCATION_TOPIC = 'owntracks/+/+'
EVENT_TOPIC = 'owntracks/+/+/event'
_LOGGER = logging.getLogger(__name__)
LOCK = threading.Lock()
def setup_scanner(hass, config, see):
""" Set up a OwnTracksks tracker. """
""" Set up an OwnTracks tracker. """
def owntracks_location_update(topic, payload, qos):
""" MQTT message received. """
@@ -28,26 +41,143 @@ def setup_scanner(hass, config, see):
data = json.loads(payload)
except ValueError:
# If invalid JSON
logging.getLogger(__name__).error(
_LOGGER.error(
'Unable to parse payload as JSON: %s', payload)
return
if not isinstance(data, dict) or data.get('_type') != 'location':
return
parts = topic.split('/')
kwargs = {
'dev_id': '{}_{}'.format(parts[1], parts[2]),
'host_name': parts[1],
'gps': (data['lat'], data['lon']),
}
if 'acc' in data:
kwargs['gps_accuracy'] = data['acc']
if 'batt' in data:
kwargs['battery'] = data['batt']
dev_id, kwargs = _parse_see_args(topic, data)
see(**kwargs)
# Block updates if we're in a region
with LOCK:
if REGIONS_ENTERED[dev_id]:
_LOGGER.debug(
"location update ignored - inside region %s",
REGIONS_ENTERED[-1])
return
see(**kwargs)
see_beacons(dev_id, kwargs)
def owntracks_event_update(topic, payload, qos):
# pylint: disable=too-many-branches
""" MQTT event (geofences) received. """
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typetransition
try:
data = json.loads(payload)
except ValueError:
# If invalid JSON
_LOGGER.error(
'Unable to parse payload as JSON: %s', payload)
return
if not isinstance(data, dict) or data.get('_type') != 'transition':
return
# OwnTracks uses - at the start of a beacon zone
# to switch on 'hold mode' - ignore this
location = data['desc'].lstrip("-")
if location.lower() == 'home':
location = STATE_HOME
dev_id, kwargs = _parse_see_args(topic, data)
if data['event'] == 'enter':
zone = hass.states.get("zone.{}".format(location))
with LOCK:
if zone is None:
if data['t'] == 'b':
# Not a HA zone, and a beacon so assume mobile
MOBILE_BEACONS_ACTIVE[dev_id].append(location)
else:
# Normal region
if not zone.attributes.get('passive'):
kwargs['location_name'] = location
regions = REGIONS_ENTERED[dev_id]
if location not in regions:
regions.append(location)
_LOGGER.info("Enter region %s", location)
_set_gps_from_zone(kwargs, zone)
see(**kwargs)
see_beacons(dev_id, kwargs)
elif data['event'] == 'leave':
regions = REGIONS_ENTERED[dev_id]
if location in regions:
regions.remove(location)
new_region = regions[-1] if regions else None
if new_region:
# Exit to previous region
zone = hass.states.get("zone.{}".format(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)
else:
_LOGGER.info("Exit to GPS")
see(**kwargs)
see_beacons(dev_id, kwargs)
beacons = MOBILE_BEACONS_ACTIVE[dev_id]
if location in beacons:
beacons.remove(location)
else:
_LOGGER.error(
'Misformatted mqtt msgs, _type=transition, event=%s',
data['event'])
return
def see_beacons(dev_id, kwargs_param):
""" Set active beacons to the current location """
kwargs = kwargs_param.copy()
for beacon in MOBILE_BEACONS_ACTIVE[dev_id]:
kwargs['dev_id'] = "{}_{}".format(BEACON_DEV_ID, beacon)
kwargs['host_name'] = beacon
see(**kwargs)
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1)
return True
def _parse_see_args(topic, data):
""" Parse the OwnTracks location parameters,
into the format see expects. """
parts = topic.split('/')
dev_id = '{}_{}'.format(parts[1], parts[2])
host_name = parts[1]
kwargs = {
'dev_id': dev_id,
'host_name': host_name,
'gps': (data['lat'], data['lon'])
}
if 'acc' in data:
kwargs['gps_accuracy'] = data['acc']
if 'batt' in data:
kwargs['battery'] = data['batt']
return dev_id, kwargs
def _set_gps_from_zone(kwargs, zone):
""" Set the see parameters from the zone parameters """
if zone is not None:
kwargs['gps'] = (
zone.attributes['latitude'],
zone.attributes['longitude'])
kwargs['gps_accuracy'] = zone.attributes['radius']
return kwargs

View File

@@ -0,0 +1,33 @@
# Describes the format for available device tracker services
see:
description: Control tracked device
fields:
mac:
description: MAC address of device
example: 'FF:FF:FF:FF:FF:FF'
dev_id:
description: Id of device (find id in known_devices.yaml)
example: 'phonedave'
host_name:
description: Hostname of device
example: 'Dave'
location_name:
description: Name of location where device is located (not_home is away)
example: 'home'
gps:
description: GPS coordinates where device is located (latitude, longitude)
example: '[51.509802, -0.086692]'
gps_accuracy:
description: Accuracy of GPS coordinates
example: '80'
battery:
description: Battery level of device
example: '100'

View File

@@ -105,8 +105,7 @@ class SnmpScanner(object):
return
if errstatus:
_LOGGER.error('SNMP error: %s at %s', errstatus.prettyPrint(),
errindex and restable[-1][int(errindex)-1]
or '?')
errindex and restable[-1][int(errindex)-1] or '?')
return
for resrow in restable:

View File

@@ -242,8 +242,8 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/cgi-bin/luci/;stok={}/admin/wireless?form=statistics' \
.format(self.host, self.stok)
url = ('http://{}/cgi-bin/luci/;stok={}/admin/wireless?'
'form=statistics').format(self.host, self.stok)
referer = 'http://{}/webpages/index.html'.format(self.host)
response = requests.post(url,

View File

@@ -11,6 +11,8 @@ import logging
import re
import threading
import requests
from homeassistant.helpers import validate_config
from homeassistant.util import sanitize_filename
@@ -30,14 +32,6 @@ def setup(hass, config):
logger = logging.getLogger(__name__)
try:
import requests
except ImportError:
logger.exception(("Failed to import requests. "
"Did you maybe not execute 'pip install requests'?"))
return False
if not validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger):
return False

View File

@@ -21,7 +21,9 @@ _LOGGER = logging.getLogger(__name__)
FRONTEND_URLS = [
URL_ROOT, '/logbook', '/history', '/map', '/devService', '/devState',
'/devEvent', '/devInfo', '/devTemplate', '/states']
'/devEvent', '/devInfo', '/devTemplate',
re.compile(r'/states(/([a-zA-Z\._\-0-9/]+)|)'),
]
_FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)

View File

@@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by update_mdi script """
VERSION = "7d76081c37634d36af21f5cc1ca79408"
VERSION = "a1a203680639ff1abcc7b68cdb29c57a"

View File

@@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "be08c5a3ce12040bbdba2db83cb1a568"
VERSION = "833d09737fec24f9219efae87c5bfd2a"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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.6.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)

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()

View File

@@ -7,20 +7,24 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/group/
"""
import homeassistant.core as ha
from homeassistant.helpers import generate_entity_id
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.entity import Entity
import homeassistant.util as util
from homeassistant.helpers.entity import (
Entity, split_entity_id, generate_entity_id)
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_ON, STATE_OFF,
STATE_HOME, STATE_NOT_HOME, STATE_OPEN, STATE_CLOSED,
STATE_UNKNOWN)
STATE_UNKNOWN, CONF_NAME, CONF_ICON)
DOMAIN = "group"
DOMAIN = 'group'
ENTITY_ID_FORMAT = DOMAIN + ".{}"
ENTITY_ID_FORMAT = DOMAIN + '.{}'
ATTR_AUTO = "auto"
CONF_ENTITIES = 'entities'
CONF_VIEW = 'view'
ATTR_AUTO = 'auto'
ATTR_ORDER = 'order'
ATTR_VIEW = 'view'
# List of ON/OFF state tuples for groupable states
_GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME),
@@ -62,12 +66,12 @@ def expand_entity_ids(hass, entity_ids):
try:
# If entity_id points at a group, expand it
domain, _ = util.split_entity_id(entity_id)
domain, _ = split_entity_id(entity_id)
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:
@@ -75,7 +79,7 @@ def expand_entity_ids(hass, entity_ids):
found_ids.append(entity_id)
except AttributeError:
# Raised by util.split_entity_id if entity_id is not a string
# Raised by split_entity_id if entity_id is not a string
pass
return found_ids
@@ -104,10 +108,20 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
def setup(hass, config):
""" Sets up all groups found definded in the configuration. """
for name, entity_ids in config.get(DOMAIN, {}).items():
for object_id, conf in config.get(DOMAIN, {}).items():
if not isinstance(conf, dict):
conf = {CONF_ENTITIES: conf}
name = conf.get(CONF_NAME, object_id)
entity_ids = conf.get(CONF_ENTITIES)
icon = conf.get(CONF_ICON)
view = conf.get(CONF_VIEW)
if isinstance(entity_ids, str):
entity_ids = [ent.strip() for ent in entity_ids.split(",")]
setup_group(hass, name, entity_ids)
Group(hass, name, entity_ids, icon=icon, view=view,
object_id=object_id)
return True
@@ -115,14 +129,19 @@ def setup(hass, config):
class Group(Entity):
""" Tracks a group of entity ids. """
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-instance-attributes, too-many-arguments
def __init__(self, hass, name, entity_ids=None, user_defined=True):
def __init__(self, hass, name, entity_ids=None, user_defined=True,
icon=None, view=False, object_id=None):
self.hass = hass
self._name = name
self._state = STATE_UNKNOWN
self.user_defined = user_defined
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=hass)
self._order = len(hass.states.entity_ids(DOMAIN))
self._user_defined = user_defined
self._icon = icon
self._view = view
self.entity_id = generate_entity_id(
ENTITY_ID_FORMAT, object_id or name, hass=hass)
self.tracking = []
self.group_on = None
self.group_off = None
@@ -144,12 +163,25 @@ class Group(Entity):
def state(self):
return self._state
@property
def icon(self):
return self._icon
@property
def hidden(self):
return not self._user_defined or self._view
@property
def state_attributes(self):
return {
data = {
ATTR_ENTITY_ID: self.tracking,
ATTR_AUTO: not self.user_defined,
ATTR_ORDER: self._order,
}
if not self._user_defined:
data[ATTR_AUTO] = True
if self._view:
data[ATTR_VIEW] = True
return data
def update_tracked_entity_ids(self, entity_ids):
""" Update the tracked entity IDs. """
@@ -220,10 +252,3 @@ class Group(Entity):
for ent_id in self.tracking
if tr_state.entity_id != ent_id):
self._state = group_off
def setup_group(hass, name, entity_ids, user_defined=True):
""" Sets up a group state that is the combined state of
several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """
return Group(hass, name, entity_ids, user_defined)

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())

View File

@@ -21,7 +21,7 @@ from urllib.parse import urlparse, parse_qs
import homeassistant.core as ha
from homeassistant.const import (
SERVER_PORT, CONTENT_TYPE_JSON,
SERVER_PORT, CONTENT_TYPE_JSON, CONTENT_TYPE_TEXT_PLAIN,
HTTP_HEADER_HA_AUTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_ACCEPT_ENCODING,
HTTP_HEADER_CONTENT_ENCODING, HTTP_HEADER_VARY, HTTP_HEADER_CONTENT_LENGTH,
HTTP_HEADER_CACHE_CONTROL, HTTP_HEADER_EXPIRES, HTTP_OK, HTTP_UNAUTHORIZED,
@@ -112,10 +112,10 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
_LOGGER.info("running http in development mode")
if ssl_certificate is not None:
wrap_kwargs = {'certfile': ssl_certificate}
if ssl_key is not None:
wrap_kwargs['keyfile'] = ssl_key
self.socket = ssl.wrap_socket(self.socket, **wrap_kwargs)
context = ssl.create_default_context(
purpose=ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(ssl_certificate, keyfile=ssl_key)
self.socket = context.wrap_socket(self.socket, server_side=True)
def start(self):
""" Starts the HTTP server. """
@@ -198,12 +198,12 @@ class RequestHandler(SimpleHTTPRequestHandler):
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
return
self.authenticated = (self.server.api_password is None
or self.headers.get(HTTP_HEADER_HA_AUTH) ==
self.server.api_password
or data.get(DATA_API_PASSWORD) ==
self.server.api_password
or self.verify_session())
self.authenticated = (self.server.api_password is None or
self.headers.get(HTTP_HEADER_HA_AUTH) ==
self.server.api_password or
data.get(DATA_API_PASSWORD) ==
self.server.api_password or
self.verify_session())
if '_METHOD' in data:
method = data.pop('_METHOD')
@@ -293,6 +293,17 @@ class RequestHandler(SimpleHTTPRequestHandler):
json.dumps(data, indent=4, sort_keys=True,
cls=rem.JSONEncoder).encode("UTF-8"))
def write_text(self, message, status_code=HTTP_OK):
""" Helper method to return a text message to the caller. """
self.send_response(status_code)
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN)
self.set_session_cookie_header()
self.end_headers()
self.wfile.write(message.encode("UTF-8"))
def write_file(self, path, cache_headers=True):
""" Returns a file to the user. """
try:

View File

@@ -7,13 +7,10 @@ For more details about this component, please refer to the documentation at
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__)
@@ -23,14 +20,18 @@ DEPENDENCIES = []
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 8086
DEFAULT_DATABASE = 'home_assistant'
DEFAULT_SSL = False
DEFAULT_VERIFY_SSL = False
REQUIREMENTS = ['influxdb==2.10.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):
@@ -38,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]
@@ -48,37 +51,36 @@ 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)
databases = [i['name'] for i in influx.get_list_database()]
except exceptions.InfluxDBClientError:
_LOGGER.error("Database host is not accessible. "
"Please check your entries in the configuration file.")
return False
if database not in databases:
_LOGGER.error("Database %s doesn't exist", 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 "
"check your entries in the configuration file and that"
" the database exists and is READ/WRITE.", exc)
return False
def influx_event_listener(event):
""" 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
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 = [
{
@@ -97,7 +99,7 @@ def setup(hass, config):
try:
influx.write_points(json_body)
except exceptions.InfluxDBClientError:
_LOGGER.exception('Error saving event to InfluxDB')
_LOGGER.exception('Error saving event "%s" to InfluxDB', json_body)
hass.bus.listen(EVENT_STATE_CHANGED, influx_event_listener)

View File

@@ -0,0 +1,126 @@
"""
homeassistant.components.input_boolean
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to keep track of user controlled booleans for within automation.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/input_boolean/
"""
import logging
from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.util import slugify
DOMAIN = 'input_boolean'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__)
CONF_NAME = "name"
CONF_INITIAL = "initial"
CONF_ICON = "icon"
def is_on(hass, entity_id):
"""Test if input_boolean is True."""
return hass.states.is_state(entity_id, STATE_ON)
def turn_on(hass, entity_id):
"""Set input_boolean to True."""
hass.services.call(DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id})
def turn_off(hass, entity_id):
"""Set input_boolean to False."""
hass.services.call(DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id})
def setup(hass, config):
""" Set up input boolean. """
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:
cfg = {}
name = cfg.get(CONF_NAME)
state = cfg.get(CONF_INITIAL, False)
icon = cfg.get(CONF_ICON)
entities.append(InputBoolean(object_id, name, state, icon))
if not entities:
return False
def toggle_service(service):
""" Handle a calls to the input boolean services. """
target_inputs = component.extract_from_service(service)
for input_b in target_inputs:
if service.service == SERVICE_TURN_ON:
input_b.turn_on()
else:
input_b.turn_off()
hass.services.register(DOMAIN, SERVICE_TURN_OFF, toggle_service)
hass.services.register(DOMAIN, SERVICE_TURN_ON, toggle_service)
component.add_entities(entities)
return True
class InputBoolean(ToggleEntity):
""" Represent a boolean input. """
def __init__(self, object_id, name, state, icon):
""" Initialize a boolean input. """
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
self._state = state
self._icon = icon
@property
def should_poll(self):
"""If entitiy should be polled."""
return False
@property
def name(self):
"""Name of the boolean input."""
return self._name
@property
def icon(self):
"""Icon to be used for this entity."""
return self._icon
@property
def is_on(self):
"""True if entity is on."""
return self._state
def turn_on(self, **kwargs):
"""Turn the entity on."""
self._state = True
self.update_ha_state()
def turn_off(self, **kwargs):
"""Turn the entity off."""
self._state = False
self.update_ha_state()

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()

View File

@@ -0,0 +1,93 @@
"""
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_hub/
"""
import logging
import homeassistant.bootstrap as bootstrap
from homeassistant.helpers import validate_config
from homeassistant.loader import get_component
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY, ATTR_DISCOVERED,
ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED)
DOMAIN = "insteon_hub"
REQUIREMENTS = ['insteon_hub==0.4.5']
INSTEON = None
DISCOVER_LIGHTS = "insteon_hub.lights"
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
"""
Setup Insteon Hub component.
This will automatically import associated lights.
"""
if not validate_config(
config,
{DOMAIN: [CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY]},
_LOGGER):
return False
import insteon
username = config[DOMAIN][CONF_USERNAME]
password = config[DOMAIN][CONF_PASSWORD]
api_key = config[DOMAIN][CONF_API_KEY]
global INSTEON
INSTEON = insteon.Insteon(username, password, api_key)
if INSTEON is None:
_LOGGER.error("Could not connect to Insteon service.")
return
comp_name = 'light'
discovery = DISCOVER_LIGHTS
component = get_component(comp_name)
bootstrap.setup_component(hass, component.DOMAIN, config)
hass.bus.fire(
EVENT_PLATFORM_DISCOVERED,
{ATTR_SERVICE: discovery, ATTR_DISCOVERED: {}})
return True
class InsteonToggleDevice(ToggleEntity):
""" Abstract Class for an Insteon node. """
def __init__(self, node):
self.node = node
self._value = 0
@property
def name(self):
""" Returns the name of the node. """
return self.node.DeviceName
@property
def unique_id(self):
""" Returns the id of this insteon node. """
return self.node.DeviceID
def update(self):
""" Update state of the sensor. """
resp = self.node.send_command('get_status', wait=True)
try:
self._value = resp['response']['level']
except KeyError:
pass
@property
def is_on(self):
""" Returns boolean response if the node is on. """
return self._value != 0
def turn_on(self, **kwargs):
self.node.send_command('on')
def turn_off(self, **kwargs):
self.node.send_command('off')

View File

@@ -37,11 +37,7 @@ def setup(hass, config):
Setup ISY994 component.
This will automatically import associated lights, switches, and sensors.
"""
try:
import PyISY
except ImportError:
_LOGGER.error("Error while importing dependency PyISY.")
return False
import PyISY
# pylint: disable=global-statement
# check for required values in configuration file

View File

@@ -6,8 +6,6 @@ Provides functionality to emulate keyboard presses on host machine.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/keyboard/
"""
import logging
from homeassistant.const import (
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK,
@@ -50,13 +48,7 @@ def media_prev_track(hass):
def setup(hass, config):
""" Listen for keyboard events. """
try:
import pykeyboard
except ImportError:
logging.getLogger(__name__).exception(
"Error while importing dependency PyUserInput.")
return False
import pykeyboard
keyboard = pykeyboard.PyKeyboard()
keyboard.special_key_assignment()

View File

@@ -10,10 +10,12 @@ import logging
import os
import csv
from homeassistant.components import group, discovery, wink, isy994, zwave
from homeassistant.components import (
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, ATTR_ENTITY_ID)
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
ATTR_ENTITY_ID)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
import homeassistant.util as util
@@ -50,6 +52,7 @@ FLASH_LONG = "long"
# Apply an effect to the light, can be EFFECT_COLORLOOP
ATTR_EFFECT = "effect"
EFFECT_COLORLOOP = "colorloop"
EFFECT_RANDOM = "random"
EFFECT_WHITE = "white"
LIGHT_PROFILES_FILE = "light_profiles.csv"
@@ -57,9 +60,11 @@ LIGHT_PROFILES_FILE = "light_profiles.csv"
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
wink.DISCOVER_LIGHTS: 'wink',
insteon_hub.DISCOVER_LIGHTS: 'insteon_hub',
isy994.DISCOVER_LIGHTS: 'isy994',
discovery.SERVICE_HUE: 'hue',
zwave.DISCOVER_LIGHTS: 'zwave',
mysensors.DISCOVER_LIGHTS: 'mysensors',
}
PROP_TO_ATTR = {
@@ -113,6 +118,18 @@ def turn_off(hass, entity_id=None, transition=None):
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
def toggle(hass, entity_id=None, transition=None):
""" Toggles all or specified light. """
data = {
key: value for key, value in [
(ATTR_ENTITY_ID, entity_id),
(ATTR_TRANSITION, transition),
] if value is not None
}
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
# pylint: disable=too-many-branches, too-many-locals, too-many-statements
def setup(hass, config):
""" Exposes light control via statemachine and services. """
@@ -164,9 +181,15 @@ def setup(hass, config):
if transition is not None:
params[ATTR_TRANSITION] = transition
service_fun = None
if service.service == SERVICE_TURN_OFF:
service_fun = 'turn_off'
elif service.service == SERVICE_TOGGLE:
service_fun = 'toggle'
if service_fun:
for light in target_lights:
light.turn_off(**params)
getattr(light, service_fun)(**params)
for light in target_lights:
if light.should_poll:
@@ -228,7 +251,8 @@ def setup(hass, config):
if dat.get(ATTR_FLASH) in (FLASH_SHORT, FLASH_LONG):
params[ATTR_FLASH] = dat[ATTR_FLASH]
if dat.get(ATTR_EFFECT) in (EFFECT_COLORLOOP, EFFECT_WHITE):
if dat.get(ATTR_EFFECT) in (EFFECT_COLORLOOP, EFFECT_WHITE,
EFFECT_RANDOM):
params[ATTR_EFFECT] = dat[ATTR_EFFECT]
for light in target_lights:
@@ -247,6 +271,9 @@ def setup(hass, config):
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_light_service,
descriptions.get(SERVICE_TURN_OFF))
hass.services.register(DOMAIN, SERVICE_TOGGLE, handle_light_service,
descriptions.get(SERVICE_TOGGLE))
return True
@@ -274,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. """
@@ -296,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

View File

@@ -10,17 +10,18 @@ import json
import logging
import os
import socket
import random
from datetime import timedelta
from urllib.parse import urlparse
from homeassistant.loader import get_component
import homeassistant.util as util
import homeassistant.util.color as color_util
from homeassistant.const import CONF_HOST, DEVICE_DEFAULT_NAME
from homeassistant.const import CONF_HOST, CONF_FILENAME, DEVICE_DEFAULT_NAME
from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_COLOR_TEMP,
ATTR_TRANSITION, ATTR_FLASH, FLASH_LONG, FLASH_SHORT,
ATTR_EFFECT, EFFECT_COLORLOOP, ATTR_RGB_COLOR)
ATTR_EFFECT, EFFECT_COLORLOOP, EFFECT_RANDOM, ATTR_RGB_COLOR)
REQUIREMENTS = ['phue==0.8']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
@@ -34,9 +35,9 @@ _CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
def _find_host_from_config(hass):
def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE):
""" Attempt to detect host based on existing configuration. """
path = hass.config.path(PHUE_CONFIG_FILE)
path = hass.config.path(filename)
if not os.path.isfile(path):
return None
@@ -53,13 +54,14 @@ def _find_host_from_config(hass):
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Gets the Hue lights. """
filename = config.get(CONF_FILENAME, PHUE_CONFIG_FILE)
if discovery_info is not None:
host = urlparse(discovery_info[1]).hostname
else:
host = config.get(CONF_HOST, None)
if host is None:
host = _find_host_from_config(hass)
host = _find_host_from_config(hass, filename)
if host is None:
_LOGGER.error('No host found in configuration')
@@ -69,17 +71,17 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
if host in _CONFIGURING:
return
setup_bridge(host, hass, add_devices_callback)
setup_bridge(host, hass, add_devices_callback, filename)
def setup_bridge(host, hass, add_devices_callback):
def setup_bridge(host, hass, add_devices_callback, filename):
""" Setup a phue bridge based on host parameter. """
import phue
try:
bridge = phue.Bridge(
host,
config_file_path=hass.config.path(PHUE_CONFIG_FILE))
config_file_path=hass.config.path(filename))
except ConnectionRefusedError: # Wrong host was given
_LOGGER.exception("Error connecting to the Hue bridge at %s", host)
@@ -88,7 +90,7 @@ def setup_bridge(host, hass, add_devices_callback):
except phue.PhueRegistrationException:
_LOGGER.warning("Connected to Hue at %s but not registered.", host)
request_configuration(host, hass, add_devices_callback)
request_configuration(host, hass, add_devices_callback, filename)
return
@@ -120,10 +122,17 @@ def setup_bridge(host, hass, add_devices_callback):
new_lights = []
api_name = api.get('config').get('name')
if api_name == 'RaspBee-GW':
bridge_type = 'deconz'
else:
bridge_type = 'hue'
for light_id, info in api_states.items():
if light_id not in lights:
lights[light_id] = HueLight(int(light_id), info,
bridge, update_lights)
bridge, update_lights,
bridge_type=bridge_type)
new_lights.append(lights[light_id])
else:
lights[light_id].info = info
@@ -134,7 +143,7 @@ def setup_bridge(host, hass, add_devices_callback):
update_lights()
def request_configuration(host, hass, add_devices_callback):
def request_configuration(host, hass, add_devices_callback, filename):
""" Request configuration steps from the user. """
configurator = get_component('configurator')
@@ -148,7 +157,7 @@ def request_configuration(host, hass, add_devices_callback):
# pylint: disable=unused-argument
def hue_configuration_callback(data):
""" Actions to do when our configuration callback is called. """
setup_bridge(host, hass, add_devices_callback)
setup_bridge(host, hass, add_devices_callback, filename)
_CONFIGURING[host] = configurator.request_config(
hass, "Philips Hue", hue_configuration_callback,
@@ -162,11 +171,14 @@ def request_configuration(host, hass, add_devices_callback):
class HueLight(Light):
""" Represents a Hue light """
def __init__(self, light_id, info, bridge, update_lights):
# pylint: disable=too-many-arguments
def __init__(self, light_id, info, bridge, update_lights,
bridge_type='hue'):
self.light_id = light_id
self.info = info
self.bridge = bridge
self.update_lights = update_lights
self.bridge_type = bridge_type
@property
def unique_id(self):
@@ -226,14 +238,17 @@ class HueLight(Light):
command['alert'] = 'lselect'
elif flash == FLASH_SHORT:
command['alert'] = 'select'
else:
elif self.bridge_type == 'hue':
command['alert'] = 'none'
effect = kwargs.get(ATTR_EFFECT)
if effect == EFFECT_COLORLOOP:
command['effect'] = 'colorloop'
else:
elif effect == EFFECT_RANDOM:
command['hue'] = random.randrange(0, 65535)
command['sat'] = random.randrange(150, 254)
elif self.bridge_type == 'hue':
command['effect'] = 'none'
self.bridge.set_light(self.light_id, command)

View File

@@ -0,0 +1,18 @@
"""
homeassistant.components.light.insteon
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Insteon Hub lights.
"""
from homeassistant.components.insteon_hub import (INSTEON, InsteonToggleDevice)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Insteon Hub light platform. """
devs = []
for device in INSTEON.devices:
if device.DeviceCategory == "Switched Lighting Control":
devs.append(InsteonToggleDevice(device))
if device.DeviceCategory == "Dimmable Lighting Control":
devs.append(InsteonToggleDevice(device))
add_devices(devs)

View File

@@ -0,0 +1,267 @@
"""
homeassistant.components.light.lifx
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LIFX platform that implements lights
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.lifx/
"""
# pylint: disable=missing-docstring
import logging
import colorsys
from homeassistant.helpers.event import track_time_change
from homeassistant.components.light import \
(Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_COLOR_TEMP, ATTR_TRANSITION)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['liffylights==0.9.4']
DEPENDENCIES = []
CONF_SERVER = "server" # server address configuration item
CONF_BROADCAST = "broadcast" # broadcast address configuration item
SHORT_MAX = 65535 # short int maximum
BYTE_MAX = 255 # byte maximum
TEMP_MIN = 2500 # lifx minimum temperature
TEMP_MAX = 9000 # lifx maximum temperature
TEMP_MIN_HASS = 154 # home assistant minimum temperature
TEMP_MAX_HASS = 500 # home assistant maximum temperature
class LIFX():
def __init__(self, add_devices_callback,
server_addr=None, broadcast_addr=None):
import liffylights
self._devices = []
self._add_devices_callback = add_devices_callback
self._liffylights = liffylights.LiffyLights(
self.on_device,
self.on_power,
self.on_color,
server_addr,
broadcast_addr)
def find_bulb(self, ipaddr):
bulb = None
for device in self._devices:
if device.ipaddr == ipaddr:
bulb = device
break
return bulb
# pylint: disable=too-many-arguments
def on_device(self, ipaddr, name, power, hue, sat, bri, kel):
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):
bulb = self.find_bulb(ipaddr)
if bulb is not None:
bulb.set_color(hue, sat, bri, kel)
bulb.update_ha_state()
def on_power(self, ipaddr, power):
bulb = self.find_bulb(ipaddr)
if bulb is not None:
bulb.set_power(power)
bulb.update_ha_state()
# pylint: disable=unused-argument
def poll(self, now):
self.probe()
def probe(self, address=None):
self._liffylights.probe(address)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Set up platform. """
server_addr = config.get(CONF_SERVER, None)
broadcast_addr = config.get(CONF_BROADCAST, None)
lifx_library = LIFX(add_devices_callback, server_addr, broadcast_addr)
# register our poll service
track_time_change(hass, lifx_library.poll, second=[10, 40])
lifx_library.probe()
def convert_rgb_to_hsv(rgb):
""" Convert HASS RGB values to HSV values. """
red, green, blue = [_ / BYTE_MAX for _ in rgb]
hue, saturation, brightness = colorsys.rgb_to_hsv(red, green, blue)
return [int(hue * SHORT_MAX),
int(saturation * SHORT_MAX),
int(brightness * SHORT_MAX)]
# pylint: disable=too-many-instance-attributes
class LIFXLight(Light):
""" Provides LIFX light. """
# pylint: disable=too-many-arguments
def __init__(self, liffy, ipaddr, name, power, hue,
saturation, brightness, kelvin):
_LOGGER.debug("LIFXLight: %s %s",
ipaddr, name)
self._liffylights = liffy
self._ip = ipaddr
self.set_name(name)
self.set_power(power)
self.set_color(hue, saturation, brightness, kelvin)
@property
def should_poll(self):
""" No polling needed for LIFX light. """
return False
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def ipaddr(self):
""" Returns the ip of the device. """
return self._ip
@property
def rgb_color(self):
""" Returns RGB value. """
_LOGGER.debug("rgb_color: [%d %d %d]",
self._rgb[0], self._rgb[1], self._rgb[2])
return self._rgb
@property
def brightness(self):
""" Returns brightness of this light between 0..255. """
brightness = int(self._bri / (BYTE_MAX + 1))
_LOGGER.debug("brightness: %d",
brightness)
return brightness
@property
def color_temp(self):
""" Returns color temperature. """
temperature = int(TEMP_MIN_HASS + (TEMP_MAX_HASS - TEMP_MIN_HASS) *
(self._kel - TEMP_MIN) / (TEMP_MAX - TEMP_MIN))
_LOGGER.debug("color_temp: %d",
temperature)
return temperature
@property
def is_on(self):
""" True if device is on. """
_LOGGER.debug("is_on: %d",
self._power)
return self._power != 0
def turn_on(self, **kwargs):
""" Turn the device on. """
if ATTR_TRANSITION in kwargs:
fade = kwargs[ATTR_TRANSITION] * 1000
else:
fade = 0
if ATTR_RGB_COLOR in kwargs:
hue, saturation, brightness = \
convert_rgb_to_hsv(kwargs[ATTR_RGB_COLOR])
else:
hue = self._hue
saturation = self._sat
brightness = self._bri
if ATTR_BRIGHTNESS in kwargs:
brightness = kwargs[ATTR_BRIGHTNESS] * (BYTE_MAX + 1)
else:
brightness = self._bri
if ATTR_COLOR_TEMP in kwargs:
kelvin = int(((TEMP_MAX - TEMP_MIN) *
(kwargs[ATTR_COLOR_TEMP] - TEMP_MIN_HASS) /
(TEMP_MAX_HASS - TEMP_MIN_HASS)) + TEMP_MIN)
else:
kelvin = self._kel
_LOGGER.debug("turn_on: %s (%d) %d %d %d %d %d",
self._ip, self._power,
hue, saturation, brightness, kelvin, fade)
if self._power == 0:
self._liffylights.set_power(self._ip, 65535, fade)
self._liffylights.set_color(self._ip, hue, saturation,
brightness, kelvin, fade)
def turn_off(self, **kwargs):
""" Turn the device off. """
if ATTR_TRANSITION in kwargs:
fade = kwargs[ATTR_TRANSITION] * 1000
else:
fade = 0
_LOGGER.debug("turn_off: %s %d",
self._ip, fade)
self._liffylights.set_power(self._ip, 0, fade)
def set_name(self, name):
""" Set name. """
self._name = name
def set_power(self, power):
""" Set power state value. """
_LOGGER.debug("set_power: %d",
power)
self._power = (power != 0)
def set_color(self, hue, sat, bri, kel):
""" Set color state values. """
self._hue = hue
self._sat = sat
self._bri = bri
self._kel = kel
red, green, blue = colorsys.hsv_to_rgb(hue / SHORT_MAX,
sat / SHORT_MAX,
bri / SHORT_MAX)
red = int(red * BYTE_MAX)
green = int(green * BYTE_MAX)
blue = int(blue * BYTE_MAX)
_LOGGER.debug("set_color: %d %d %d %d [%d %d %d]",
hue, sat, bri, kel, red, green, blue)
self._rgb = [red, green, blue]

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

View File

@@ -9,12 +9,13 @@ https://home-assistant.io/components/light.rfxtrx/
import logging
import homeassistant.components.rfxtrx as rfxtrx
from homeassistant.components.light import Light
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
from homeassistant.util import slugify
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.components.rfxtrx import ATTR_STATE, ATTR_FIREEVENT, ATTR_PACKETID, \
ATTR_NAME, EVENT_BUTTON_PRESSED
from homeassistant.components.rfxtrx import (
ATTR_STATE, ATTR_FIREEVENT, ATTR_PACKETID,
ATTR_NAME, EVENT_BUTTON_PRESSED)
DEPENDENCIES = ['rfxtrx']
@@ -49,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
@@ -73,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':
@@ -89,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:
@@ -111,6 +125,7 @@ class RfxtrxLight(Light):
self._event = event
self._state = datas[ATTR_STATE]
self._should_fire_event = datas[ATTR_FIREEVENT]
self._brightness = 0
@property
def should_poll(self):
@@ -132,12 +147,25 @@ class RfxtrxLight(Light):
""" True if light is on. """
return self._state
@property
def brightness(self):
""" Brightness of this light between 0..255. """
return self._brightness
def turn_on(self, **kwargs):
""" Turn the light on. """
brightness = kwargs.get(ATTR_BRIGHTNESS)
if brightness is None:
self._brightness = 100
else:
self._brightness = ((brightness + 4) * 100 // 255 - 1)
if hasattr(self, '_event') and self._event:
self._event.device.send_on(rfxtrx.RFXOBJECT.transport)
self._event.device.send_dim(rfxtrx.RFXOBJECT.transport,
self._brightness)
self._brightness = (self._brightness * 255 // 100)
self._state = True
self.update_ha_state()
@@ -147,5 +175,6 @@ class RfxtrxLight(Light):
if hasattr(self, '_event') and self._event:
self._event.device.send_off(rfxtrx.RFXOBJECT.transport)
self._brightness = 0
self._state = False
self.update_ha_state()

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
}
)

View File

@@ -42,6 +42,7 @@ turn_on:
description: Light effect
values:
- colorloop
- random
turn_off:
description: Turn a light off
@@ -54,3 +55,15 @@ turn_off:
transition:
description: Duration in seconds it takes to get to next state
example: 60
toggle:
description: Toggles a light
fields:
entity_id:
description: Name(s) of entities to toggle
example: 'light.kitchen'
transition:
description: Duration in seconds it takes to get to next state
example: 60

View File

@@ -7,16 +7,22 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.vera/
"""
import logging
import time
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
REQUIREMENTS = ['https://github.com/pavoni/home-assistant-vera-api/archive/'
'efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip'
'#python-vera==0.1.1']
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.8']
_LOGGER = logging.getLogger(__name__)
@@ -36,10 +42,19 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
device_data = config.get('device_data', {})
controller = veraApi.VeraController(base_url)
vera_controller, created = veraApi.init_controller(base_url)
if created:
def stop_subscription(event):
""" Shutdown Vera subscriptions and subscription thread on exit"""
_LOGGER.info("Shutting down subscriptions.")
vera_controller.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
devices = []
try:
devices = controller.get_devices([
devices = vera_controller.get_devices([
'Switch',
'On/Off Switch',
'Dimmable Switch'])
@@ -50,26 +65,44 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
lights = []
for device in devices:
extra_data = device_data.get(device.deviceId, {})
extra_data = device_data.get(device.device_id, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
lights.append(VeraLight(device, extra_data))
lights.append(VeraLight(device, vera_controller, extra_data))
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:
@@ -77,5 +110,51 @@ class VeraLight(VeraSwitch):
else:
self.vera_device.switch_on()
self.last_command_send = time.time()
self.is_on_status = True
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

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.3.1']
REQUIREMENTS = ['python-wink==0.6.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()

View File

@@ -0,0 +1,29 @@
"""
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)
DEPENDENCIES = ["zigbee"]
def setup_platform(hass, config, add_entities, discovery_info=None):
""" Create and add an entity based on the configuration. """
add_entities([
ZigBeeLight(hass, ZigBeeDigitalOutConfig(config))
])
class ZigBeeLight(ZigBeeDigitalOut, Light):
"""
Use multiple inheritance to turn an instance of ZigBeeDigitalOut into a
Light.
"""
pass

View File

@@ -11,8 +11,10 @@ https://home-assistant.io/components/light.zwave/
from threading import Timer
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.components.light import (Light, ATTR_BRIGHTNESS)
import homeassistant.components.zwave as zwave
from homeassistant.components.light import Light, ATTR_BRIGHTNESS, DOMAIN
from homeassistant.components.zwave import (
COMMAND_CLASS_SWITCH_MULTILEVEL, TYPE_BYTE, GENRE_USER, NETWORK,
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -20,14 +22,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
value = node.values[discovery_info[ATTR_VALUE_ID]]
if value.command_class != zwave.COMMAND_CLASS_SWITCH_MULTILEVEL:
if value.command_class != COMMAND_CLASS_SWITCH_MULTILEVEL:
return
if value.type != zwave.TYPE_BYTE:
if value.type != TYPE_BYTE:
return
if value.genre != zwave.GENRE_USER:
if value.genre != GENRE_USER:
return
value.set_change_verified(False)
@@ -45,15 +47,14 @@ def brightness_state(value):
return 255, STATE_OFF
class ZwaveDimmer(Light):
class ZwaveDimmer(ZWaveDeviceEntity, Light):
""" Provides a Z-Wave dimmer. """
# pylint: disable=too-many-arguments
def __init__(self, value):
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
self._value = value
self._node = value.node
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._brightness, self._state = brightness_state(value)
@@ -86,18 +87,6 @@ class ZwaveDimmer(Light):
self.update_ha_state()
@property
def should_poll(self):
""" No polling needed for a light. """
return False
@property
def name(self):
""" Returns the name of the device if any. """
name = self._node.name or "{}".format(self._node.product_name)
return "{}".format(name or self._value.label)
@property
def brightness(self):
""" Brightness of this light between 0..255. """
@@ -118,10 +107,10 @@ class ZwaveDimmer(Light):
# brightness.
brightness = (self._brightness / 255) * 99
if self._node.set_dimmer(self._value.value_id, brightness):
if self._value.node.set_dimmer(self._value.value_id, brightness):
self._state = STATE_ON
def turn_off(self, **kwargs):
""" Turn the device off. """
if self._node.set_dimmer(self._value.value_id, 0):
if self._value.node.set_dimmer(self._value.value_id, 0):
self._state = STATE_OFF

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

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()

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.3.1']
REQUIREMENTS = ['python-wink==0.6.0']
def setup_platform(hass, config, add_devices, discovery_info=None):

View File

@@ -6,6 +6,7 @@ Parses events and generates a human log.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/logbook/
"""
import logging
from datetime import timedelta
from itertools import groupby
import re
@@ -14,10 +15,10 @@ from homeassistant.core import State, DOMAIN as HA_DOMAIN
from homeassistant.const import (
EVENT_STATE_CHANGED, STATE_NOT_HOME, STATE_ON, STATE_OFF,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST)
from homeassistant import util
import homeassistant.util.dt as dt_util
from homeassistant.components import recorder, sun
from homeassistant.helpers.entity import split_entity_id
from homeassistant.util import template
DOMAIN = "logbook"
DEPENDENCIES = ['recorder', 'http']
@@ -28,7 +29,9 @@ QUERY_EVENTS_BETWEEN = """
SELECT * FROM events WHERE time_fired > ? AND time_fired < ?
"""
EVENT_LOGBOOK_ENTRY = 'LOGBOOK_ENTRY'
_LOGGER = logging.getLogger(__name__)
EVENT_LOGBOOK_ENTRY = 'logbook_entry'
GROUP_BY_MINUTES = 15
@@ -54,8 +57,22 @@ def log_entry(hass, name, message, domain=None, entity_id=None):
def setup(hass, config):
""" Listens for download events to download files. """
hass.http.register_path('GET', URL_LOGBOOK, _handle_get_logbook)
# create service handler
def log_message(service):
""" Handle sending notification message service calls. """
message = service.data.get(ATTR_MESSAGE)
name = service.data.get(ATTR_NAME)
domain = service.data.get(ATTR_DOMAIN, None)
entity_id = service.data.get(ATTR_ENTITY_ID, None)
if not message or not name:
return
message = template.render(hass, message)
log_entry(hass, name, message, domain, entity_id)
hass.http.register_path('GET', URL_LOGBOOK, _handle_get_logbook)
hass.services.register(DOMAIN, 'log', log_message)
return True
@@ -204,12 +221,12 @@ def humanify(events):
event.time_fired, "Home Assistant", action,
domain=HA_DOMAIN)
elif event.event_type == EVENT_LOGBOOK_ENTRY:
elif event.event_type.lower() == EVENT_LOGBOOK_ENTRY:
domain = event.data.get(ATTR_DOMAIN)
entity_id = event.data.get(ATTR_ENTITY_ID)
if domain is None and entity_id is not None:
try:
domain = util.split_entity_id(str(entity_id))[0]
domain = split_entity_id(str(entity_id))[0]
except IndexError:
pass

View File

@@ -76,8 +76,12 @@ def setup(hass, config=None):
logfilter[LOGGER_LOGS] = logs
logger = logging.getLogger('')
logger.setLevel(logging.NOTSET)
# Set log filter for all log handler
for handler in logging.root.handlers:
handler.setLevel(logging.NOTSET)
handler.addFilter(HomeAssistantLogFilter(logfilter))
return True

View File

@@ -14,10 +14,10 @@ from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.const import (
STATE_OFF, STATE_UNKNOWN, STATE_PLAYING,
STATE_OFF, STATE_UNKNOWN, STATE_PLAYING, STATE_IDLE,
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET,
SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_MUTE, SERVICE_TOGGLE,
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
@@ -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,16 +67,16 @@ 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
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'
SUPPORT_VOLUME_STEP = 1024
SERVICE_TO_METHOD = {
SERVICE_TURN_ON: 'turn_on',
SERVICE_TURN_OFF: 'turn_off',
SERVICE_TOGGLE: 'toggle',
SERVICE_VOLUME_UP: 'volume_up',
SERVICE_VOLUME_DOWN: 'volume_down',
SERVICE_MEDIA_PLAY_PAUSE: 'media_play_pause',
@@ -130,6 +129,12 @@ def turn_off(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
def toggle(hass, entity_id=None):
""" Will toggle specified media player or all. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
def volume_up(hass, entity_id=None):
""" Send the media player the command for volume up. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
@@ -192,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}
@@ -275,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)
@@ -283,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')
@@ -314,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))
@@ -441,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()
@@ -482,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()
@@ -521,16 +496,18 @@ 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. """
return bool(self.supported_media_commands & SUPPORT_PLAY_MEDIA)
def toggle(self):
""" Toggles the power on the media player. """
if self.state in [STATE_OFF, STATE_IDLE]:
self.turn_on()
else:
self.turn_off()
def volume_up(self):
""" volume_up media player. """
if self.volume_level < 1:
@@ -564,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

View File

@@ -16,60 +16,57 @@ 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)
REQUIREMENTS = ['pychromecast==0.6.13']
REQUIREMENTS = ['pychromecast==0.7.1']
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):

View File

@@ -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:

View File

@@ -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")

View File

@@ -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()

View File

@@ -14,8 +14,7 @@ from homeassistant.components.media_player import (
MediaPlayerDevice, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_PAUSE,
SUPPORT_SEEK, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON,
SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA,
ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_MEDIA_COMMANDS)
SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA)
from homeassistant.const import (
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_ON)
@@ -40,7 +39,10 @@ class Itunes(object):
@property
def _base_url(self):
""" Returns the base url for endpoints. """
return self.host + ":" + str(self.port)
if self.port:
return self.host + ":" + str(self.port)
else:
return self.host
def _request(self, method, path, params=None):
""" Makes the actual request and returns the parsed response. """
@@ -380,6 +382,14 @@ class AirPlayDevice(MediaPlayerDevice):
""" Returns the name of the device. """
return self.device_name
@property
def icon(self):
""" Icon to use in the frontend, if any. """
if self.selected is True:
return "mdi:volume-high"
else:
return "mdi:volume-off"
@property
def state(self):
""" Returns the state of the device. """
@@ -405,23 +415,6 @@ class AirPlayDevice(MediaPlayerDevice):
""" Flags of media commands that are supported. """
return SUPPORT_AIRPLAY
@property
def device_state_attributes(self):
""" Return the state attributes. """
state_attr = {}
state_attr[ATTR_SUPPORTED_MEDIA_COMMANDS] = SUPPORT_AIRPLAY
if self.state == STATE_OFF:
state_attr[ATTR_ENTITY_PICTURE] = \
('https://cloud.githubusercontent.com/assets/260/9833073'
'/6eb5c906-5958-11e5-9b4a-472cdf36be16.png')
else:
state_attr[ATTR_ENTITY_PICTURE] = \
('https://cloud.githubusercontent.com/assets/260/9833072'
'/6eb13cce-5958-11e5-996f-e2aaefbc9a24.png')
return state_attr
def set_volume_level(self, volume):
""" set volume level, range 0..1. """
volume = int(volume * 100)

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
@@ -72,7 +70,8 @@ class KodiDevice(MediaPlayerDevice):
try:
return self._server.Player.GetActivePlayers()
except jsonrpc_requests.jsonrpc.TransportError:
_LOGGER.exception('Unable to fetch kodi data')
_LOGGER.warning('Unable to fetch kodi data')
_LOGGER.debug('Unable to fetch kodi data', exc_info=True)
return None
@property
@@ -155,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):
@@ -262,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()

View File

@@ -9,11 +9,6 @@ https://home-assistant.io/components/media_player.mpd/
import logging
import socket
try:
import mpd
except ImportError:
mpd = None
from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
@@ -40,10 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
location = config.get('location', 'MPD')
password = config.get('password', None)
global mpd # pylint: disable=invalid-name
if mpd is None:
import mpd as mpd_
mpd = mpd_
import mpd
# pylint: disable=no-member
try:
@@ -82,6 +74,8 @@ class MpdDevice(MediaPlayerDevice):
# pylint: disable=no-member, abstract-method
def __init__(self, server, port, location, password):
import mpd
self.server = server
self.port = port
self._name = location
@@ -95,6 +89,7 @@ class MpdDevice(MediaPlayerDevice):
self.update()
def update(self):
import mpd
try:
self.status = self.client.status()
self.currentsong = self.client.currentsong()
@@ -142,10 +137,14 @@ class MpdDevice(MediaPlayerDevice):
def media_title(self):
""" Title of current playing media. """
name = self.currentsong.get('name', None)
title = self.currentsong['title']
title = self.currentsong.get('title', None)
if name is None:
if name is None and title is None:
return "None"
elif name is None:
return title
elif title is None:
return name
else:
return '{}: {}'.format(name, title)

View File

@@ -35,7 +35,7 @@ SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
def config_from_file(filename, config=None):
''' Small configuration file management function'''
""" Small configuration file management function. """
if config:
# We're writing configuration
try:
@@ -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. """
@@ -85,7 +85,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-branches
def setup_plexserver(host, token, hass, add_devices_callback):
''' Setup a plexserver based on host parameter'''
""" Setup a plexserver based on host parameter. """
import plexapi.server
import plexapi.exceptions
@@ -112,7 +112,7 @@ def setup_plexserver(host, token, hass, add_devices_callback):
{host: {'token': token}}):
_LOGGER.error('failed to save config file')
_LOGGER.info('Connected to: htts://%s', host)
_LOGGER.info('Connected to: http://%s', host)
plex_clients = {}
plex_sessions = {}

View File

@@ -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")

View File

@@ -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)

View File

@@ -38,12 +38,23 @@ SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Sonos platform. """
import soco
import socket
if discovery_info:
add_devices([SonosDevice(hass, soco.SoCo(discovery_info))])
return True
players = soco.discover()
players = None
hosts = config.get('hosts', None)
if hosts:
players = []
for host in hosts.split(","):
host = socket.gethostbyname(host)
players.append(soco.SoCo(host))
if not players:
players = soco.discover(interface_addr=config.get('interface_addr',
None))
if not players:
_LOGGER.warning('No Sonos speakers found.')

View File

@@ -22,12 +22,11 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK |\
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
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
@@ -201,13 +200,20 @@ class SqueezeBoxDevice(MediaPlayerDevice):
def media_image_url(self):
""" Image url of current playing media. """
if 'artwork_url' in self._status:
return self._status['artwork_url']
return 'http://{server}:{port}/music/current/cover.jpg?player={player}'\
.format(
server=self._lms.host,
port=self._lms.http_port,
media_url = self._status['artwork_url']
elif 'id' in self._status:
media_url = ('/music/{track_id}/cover.jpg').format(
track_id=self._status['id'])
else:
media_url = ('/music/current/cover.jpg?player={player}').format(
player=self._id)
base_url = 'http://{server}:{port}/'.format(
server=self._lms.host,
port=self._lms.http_port)
return urllib.parse.urljoin(base_url, media_url)
@property
def media_title(self):
""" Title of current playing media. """
@@ -285,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()

View File

@@ -0,0 +1,424 @@
"""
homeassistant.components.media_player.universal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Combines multiple media players into one for a universal controller.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.universal/
"""
# pylint: disable=import-error
from copy import copy
import logging
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.service import call_from_config
from homeassistant.const import (
STATE_IDLE, STATE_ON, STATE_OFF, CONF_NAME,
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE,
SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET,
SERVICE_VOLUME_MUTE,
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
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,
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,
ATTR_MEDIA_TRACK, ATTR_MEDIA_SERIES_TITLE, ATTR_MEDIA_ALBUM_ARTIST,
ATTR_MEDIA_SEASON, ATTR_MEDIA_EPISODE, ATTR_MEDIA_CHANNEL,
ATTR_MEDIA_PLAYLIST, ATTR_APP_ID, ATTR_APP_NAME, ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_SEEK_POSITION)
ATTR_ACTIVE_CHILD = 'active_child'
CONF_ATTRS = 'attributes'
CONF_CHILDREN = 'children'
CONF_COMMANDS = 'commands'
CONF_PLATFORM = 'platform'
CONF_SERVICE = 'service'
CONF_SERVICE_DATA = 'service_data'
CONF_STATE = 'state'
OFF_STATES = [STATE_IDLE, STATE_OFF]
REQUIREMENTS = []
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" sets up the universal media players """
if not validate_config(config):
return
player = UniversalMediaPlayer(hass,
config[CONF_NAME],
config[CONF_CHILDREN],
config[CONF_COMMANDS],
config[CONF_ATTRS])
add_devices([player])
def validate_config(config):
""" validate universal media player configuration """
del config[CONF_PLATFORM]
# validate name
if CONF_NAME not in config:
_LOGGER.error('Universal Media Player configuration requires name')
return False
validate_children(config)
validate_commands(config)
validate_attributes(config)
del_keys = []
for key in config:
if key not in [CONF_NAME, CONF_CHILDREN, CONF_COMMANDS, CONF_ATTRS]:
_LOGGER.warning(
'Universal Media Player (%s) unrecognized parameter %s',
config[CONF_NAME], key)
del_keys.append(key)
for key in del_keys:
del config[key]
return True
def validate_children(config):
""" validate children """
if CONF_CHILDREN not in config:
_LOGGER.info(
'No children under Universal Media Player (%s)', config[CONF_NAME])
config[CONF_CHILDREN] = []
elif not isinstance(config[CONF_CHILDREN], list):
_LOGGER.warning(
'Universal Media Player (%s) children not list in config. '
'They will be ignored.',
config[CONF_NAME])
config[CONF_CHILDREN] = []
def validate_commands(config):
""" validate commands """
if CONF_COMMANDS not in config:
config[CONF_COMMANDS] = {}
elif not isinstance(config[CONF_COMMANDS], dict):
_LOGGER.warning(
'Universal Media Player (%s) specified commands not dict in '
'config. They will be ignored.',
config[CONF_NAME])
config[CONF_COMMANDS] = {}
def validate_attributes(config):
""" validate attributes """
if CONF_ATTRS not in config:
config[CONF_ATTRS] = {}
elif not isinstance(config[CONF_ATTRS], dict):
_LOGGER.warning(
'Universal Media Player (%s) specified attributes '
'not dict in config. They will be ignored.',
config[CONF_NAME])
config[CONF_ATTRS] = {}
for key, val in config[CONF_ATTRS].items():
attr = val.split('|', 1)
if len(attr) == 1:
attr.append(None)
config[CONF_ATTRS][key] = attr
class UniversalMediaPlayer(MediaPlayerDevice):
""" Represents a universal media player in HA """
# pylint: disable=too-many-public-methods
def __init__(self, hass, name, children, commands, attributes):
# pylint: disable=too-many-arguments
self.hass = hass
self._name = name
self._children = children
self._cmds = commands
self._attrs = attributes
self._child_state = None
def on_dependency_update(*_):
""" update ha state when dependencies update """
self.update_ha_state(True)
depend = copy(children)
for entity in attributes.values():
depend.append(entity[0])
track_state_change(hass, depend, on_dependency_update)
def _entity_lkp(self, entity_id, state_attr=None):
""" Looks up an entity state from hass """
state_obj = self.hass.states.get(entity_id)
if state_obj is None:
return
if state_attr:
return state_obj.attributes.get(state_attr)
return state_obj.state
def _override_or_child_attr(self, attr_name):
""" returns either the override or the active child for attr_name """
if attr_name in self._attrs:
return self._entity_lkp(self._attrs[attr_name][0],
self._attrs[attr_name][1])
return self._child_attr(attr_name)
def _child_attr(self, attr_name):
""" returns the active child's attr """
active_child = self._child_state
return active_child.attributes.get(attr_name) if active_child else None
def _call_service(self, service_name, service_data=None,
allow_override=False):
""" calls either a specified or active child's service """
if allow_override and service_name in self._cmds:
call_from_config(
self.hass, self._cmds[service_name], blocking=True)
return
if service_data is None:
service_data = {}
active_child = self._child_state
service_data[ATTR_ENTITY_ID] = active_child.entity_id
self.hass.services.call(DOMAIN, service_name, service_data,
blocking=True)
@property
def should_poll(self):
""" Indicates whether HA should poll for updates """
return False
@property
def master_state(self):
""" gets the master state from entity or none """
if CONF_STATE in self._attrs:
master_state = self._entity_lkp(self._attrs[CONF_STATE][0],
self._attrs[CONF_STATE][1])
return master_state if master_state else STATE_OFF
else:
return None
@property
def name(self):
""" name of universal player """
return self._name
@property
def state(self):
"""
Current state of media player
Off if master state is off
ELSE Status of first active child
ELSE master state or off
"""
master_state = self.master_state # avoid multiple lookups
if master_state == STATE_OFF:
return STATE_OFF
active_child = self._child_state
if active_child:
return active_child.state
return master_state if master_state else STATE_OFF
@property
def volume_level(self):
""" Volume level of entity specified in attributes or active child """
return self._child_attr(ATTR_MEDIA_VOLUME_LEVEL)
@property
def is_volume_muted(self):
""" boolean if volume is muted """
return self._override_or_child_attr(ATTR_MEDIA_VOLUME_MUTED) \
in [True, STATE_ON]
@property
def media_content_id(self):
""" Content ID of current playing media. """
return self._child_attr(ATTR_MEDIA_CONTENT_ID)
@property
def media_content_type(self):
""" Content type of current playing media. """
return self._child_attr(ATTR_MEDIA_CONTENT_TYPE)
@property
def media_duration(self):
""" Duration of current playing media in seconds. """
return self._child_attr(ATTR_MEDIA_DURATION)
@property
def media_image_url(self):
""" Image url of current playing media. """
return self._child_attr(ATTR_ENTITY_PICTURE)
@property
def media_title(self):
""" Title of current playing media. """
return self._child_attr(ATTR_MEDIA_TITLE)
@property
def media_artist(self):
""" Artist of current playing media. (Music track only) """
return self._child_attr(ATTR_MEDIA_ARTIST)
@property
def media_album_name(self):
""" Album name of current playing media. (Music track only) """
return self._child_attr(ATTR_MEDIA_ALBUM_NAME)
@property
def media_album_artist(self):
""" Album arist of current playing media. (Music track only) """
return self._child_attr(ATTR_MEDIA_ALBUM_ARTIST)
@property
def media_track(self):
""" Track number of current playing media. (Music track only) """
return self._child_attr(ATTR_MEDIA_TRACK)
@property
def media_series_title(self):
""" Series title of current playing media. (TV Show only)"""
return self._child_attr(ATTR_MEDIA_SERIES_TITLE)
@property
def media_season(self):
""" Season of current playing media. (TV Show only) """
return self._child_attr(ATTR_MEDIA_SEASON)
@property
def media_episode(self):
""" Episode of current playing media. (TV Show only) """
return self._child_attr(ATTR_MEDIA_EPISODE)
@property
def media_channel(self):
""" Channel currently playing. """
return self._child_attr(ATTR_MEDIA_CHANNEL)
@property
def media_playlist(self):
""" Title of Playlist currently playing. """
return self._child_attr(ATTR_MEDIA_PLAYLIST)
@property
def app_id(self):
""" ID of the current running app. """
return self._child_attr(ATTR_APP_ID)
@property
def app_name(self):
""" Name of the current running app. """
return self._child_attr(ATTR_APP_NAME)
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
flags = self._child_attr(ATTR_SUPPORTED_MEDIA_COMMANDS) or 0
if SERVICE_TURN_ON in self._cmds:
flags |= SUPPORT_TURN_ON
if SERVICE_TURN_OFF in self._cmds:
flags |= SUPPORT_TURN_OFF
if any([cmd in self._cmds for cmd in [SERVICE_VOLUME_UP,
SERVICE_VOLUME_DOWN]]):
flags |= SUPPORT_VOLUME_STEP
flags &= ~SUPPORT_VOLUME_SET
if SERVICE_VOLUME_MUTE in self._cmds and \
ATTR_MEDIA_VOLUME_MUTED in self._attrs:
flags |= SUPPORT_VOLUME_MUTE
return flags
@property
def device_state_attributes(self):
""" Extra attributes a device wants to expose. """
active_child = self._child_state
return {ATTR_ACTIVE_CHILD: active_child.entity_id} \
if active_child else {}
def turn_on(self):
""" turn the media player on. """
self._call_service(SERVICE_TURN_ON, allow_override=True)
def turn_off(self):
""" turn the media player off. """
self._call_service(SERVICE_TURN_OFF, allow_override=True)
def mute_volume(self, is_volume_muted):
""" mute the volume. """
data = {ATTR_MEDIA_VOLUME_MUTED: is_volume_muted}
self._call_service(SERVICE_VOLUME_MUTE, data, allow_override=True)
def set_volume_level(self, volume_level):
""" set volume level, range 0..1. """
data = {ATTR_MEDIA_VOLUME_LEVEL: volume_level}
self._call_service(SERVICE_VOLUME_SET, data)
def media_play(self):
""" Send play commmand. """
self._call_service(SERVICE_MEDIA_PLAY)
def media_pause(self):
""" Send pause command. """
self._call_service(SERVICE_MEDIA_PAUSE)
def media_previous_track(self):
""" Send previous track command. """
self._call_service(SERVICE_MEDIA_PREVIOUS_TRACK)
def media_next_track(self):
""" Send next track command. """
self._call_service(SERVICE_MEDIA_NEXT_TRACK)
def media_seek(self, position):
""" Send seek command. """
data = {ATTR_MEDIA_SEEK_POSITION: position}
self._call_service(SERVICE_MEDIA_SEEK, data)
def play_media(self, media_type, media_id):
""" Plays a piece of media. """
data = {'media_type': media_type, 'media_id': media_id}
self._call_service(SERVICE_PLAY_MEDIA, data)
def volume_up(self):
""" volume_up media player. """
self._call_service(SERVICE_VOLUME_UP, allow_override=True)
def volume_down(self):
""" volume_down media player. """
self._call_service(SERVICE_VOLUME_DOWN, allow_override=True)
def media_play_pause(self):
""" media_play_pause media player. """
self._call_service(SERVICE_MEDIA_PLAY_PAUSE)
def update(self):
""" event to trigger a state update in HA """
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

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)
@@ -24,13 +26,8 @@ DOMAIN = "mqtt"
MQTT_CLIENT = None
DEFAULT_PORT = 1883
DEFAULT_KEEPALIVE = 60
DEFAULT_QOS = 0
DEFAULT_RETAIN = False
SERVICE_PUBLISH = 'publish'
EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED'
EVENT_MQTT_MESSAGE_RECEIVED = 'mqtt_message_received'
REQUIREMENTS = ['paho-mqtt==1.1']
@@ -41,34 +38,54 @@ CONF_KEEPALIVE = 'keepalive'
CONF_USERNAME = 'username'
CONF_PASSWORD = 'password'
CONF_CERTIFICATE = 'certificate'
CONF_PROTOCOL = 'protocol'
PROTOCOL_31 = '3.1'
PROTOCOL_311 = '3.1.1'
DEFAULT_PORT = 1883
DEFAULT_KEEPALIVE = 60
DEFAULT_QOS = 0
DEFAULT_RETAIN = False
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):
""" Send an MQTT message. """
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)
def subscribe(hass, topic, callback, qos=DEFAULT_QOS):
""" Subscribe to a topic. """
"""Subscribe to an MQTT topic."""
def mqtt_topic_subscriber(event):
""" Match subscribed MQTT topic. """
"""Match subscribed MQTT topic."""
if _match_topic(topic, event.data[ATTR_TOPIC]):
callback(event.data[ATTR_TOPIC], event.data[ATTR_PAYLOAD],
event.data[ATTR_QOS])
@@ -78,8 +95,7 @@ def subscribe(hass, topic, callback, qos=DEFAULT_QOS):
def setup(hass, config):
""" Get the MQTT protocol service. """
"""Start the MQTT protocol service."""
if not validate_config(config, {DOMAIN: ['broker']}, _LOGGER):
return False
@@ -92,6 +108,12 @@ def setup(hass, config):
username = util.convert(conf.get(CONF_USERNAME), str)
password = util.convert(conf.get(CONF_PASSWORD), str)
certificate = util.convert(conf.get(CONF_CERTIFICATE), str)
protocol = util.convert(conf.get(CONF_PROTOCOL), str, DEFAULT_PROTOCOL)
if protocol not in (PROTOCOL_31, PROTOCOL_311):
_LOGGER.error('Invalid protocol specified: %s. Allowed values: %s, %s',
protocol, PROTOCOL_31, PROTOCOL_311)
return False
# For cloudmqtt.com, secured connection, auto fill in certificate
if certificate is None and 19999 < port < 30000 and \
@@ -102,7 +124,7 @@ def setup(hass, config):
global MQTT_CLIENT
try:
MQTT_CLIENT = MQTT(hass, broker, port, client_id, keepalive, username,
password, certificate)
password, certificate, protocol)
except socket.error:
_LOGGER.exception("Can't connect to the broker. "
"Please check your settings and the broker "
@@ -110,175 +132,207 @@ def setup(hass, config):
return False
def stop_mqtt(event):
""" Stop MQTT component. """
"""Stop MQTT component."""
MQTT_CLIENT.stop()
def start_mqtt(event):
""" Launch MQTT component when Home Assistant starts up. """
"""Launch MQTT component when Home Assistant starts up."""
MQTT_CLIENT.start()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_mqtt)
def publish_service(call):
""" Handle MQTT publish service calls. """
"""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
# pylint: disable=too-many-arguments
class MQTT(object):
""" Implements messaging service for MQTT. """
"""Home Assistant MQTT client."""
def __init__(self, hass, broker, port, client_id, keepalive, username,
password, certificate):
password, certificate, protocol):
"""Initialize Home Assistant MQTT client."""
import paho.mqtt.client as mqtt
self.userdata = {
'hass': hass,
'topics': {},
'progress': {},
}
self.hass = hass
self.topics = {}
self.progress = {}
if protocol == PROTOCOL_31:
proto = mqtt.MQTTv31
else:
proto = mqtt.MQTTv311
if client_id is None:
self._mqttc = mqtt.Client()
self._mqttc = mqtt.Client(protocol=proto)
else:
self._mqttc = mqtt.Client(client_id)
self._mqttc.user_data_set(self.userdata)
self._mqttc = mqtt.Client(client_id, protocol=proto)
if username is not None:
self._mqttc.username_pw_set(username, password)
if certificate is not None:
self._mqttc.tls_set(certificate)
self._mqttc.on_subscribe = _mqtt_on_subscribe
self._mqttc.on_unsubscribe = _mqtt_on_unsubscribe
self._mqttc.on_connect = _mqtt_on_connect
self._mqttc.on_disconnect = _mqtt_on_disconnect
self._mqttc.on_message = _mqtt_on_message
self._mqttc.on_subscribe = self._mqtt_on_subscribe
self._mqttc.on_unsubscribe = self._mqtt_on_unsubscribe
self._mqttc.on_connect = self._mqtt_on_connect
self._mqttc.on_disconnect = self._mqtt_on_disconnect
self._mqttc.on_message = self._mqtt_on_message
self._mqttc.connect(broker, port, keepalive)
def publish(self, topic, payload, qos, retain):
""" Publish a MQTT message. """
"""Publish a MQTT message."""
self._mqttc.publish(topic, payload, qos, retain)
def start(self):
""" Run the MQTT client. """
"""Run the MQTT client."""
self._mqttc.loop_start()
def stop(self):
""" Stop the MQTT client. """
"""Stop the MQTT client."""
self._mqttc.disconnect()
self._mqttc.loop_stop()
def subscribe(self, topic, qos):
""" Subscribe to a topic. """
if topic in self.userdata['topics']:
"""Subscribe to a topic."""
assert isinstance(topic, str)
if topic in self.topics:
return
result, mid = self._mqttc.subscribe(topic, qos)
_raise_on_error(result)
self.userdata['progress'][mid] = topic
self.userdata['topics'][topic] = None
self.progress[mid] = topic
self.topics[topic] = None
def unsubscribe(self, topic):
""" Unsubscribe from topic. """
"""Unsubscribe from topic."""
result, mid = self._mqttc.unsubscribe(topic)
_raise_on_error(result)
self.userdata['progress'][mid] = topic
self.progress[mid] = topic
def _mqtt_on_connect(self, _mqttc, _userdata, _flags, result_code):
"""On connect callback.
def _mqtt_on_message(mqttc, userdata, msg):
""" Message callback """
userdata['hass'].bus.fire(EVENT_MQTT_MESSAGE_RECEIVED, {
ATTR_TOPIC: msg.topic,
ATTR_QOS: msg.qos,
ATTR_PAYLOAD: msg.payload.decode('utf-8'),
})
Resubscribe to all topics we were subscribed to.
"""
if result_code != 0:
_LOGGER.error('Unable to connect to the MQTT broker: %s', {
1: 'Incorrect protocol version',
2: 'Invalid client identifier',
3: 'Server unavailable',
4: 'Bad username or password',
5: 'Not authorised'
}.get(result_code, 'Unknown reason'))
self._mqttc.disconnect()
return
old_topics = self.topics
def _mqtt_on_connect(mqttc, userdata, flags, result_code):
""" On connect, resubscribe to all topics we were subscribed to. """
if result_code != 0:
_LOGGER.error('Unable to connect to the MQTT broker: %s', {
1: 'Incorrect protocol version',
2: 'Invalid client identifier',
3: 'Server unavailable',
4: 'Bad username or password',
5: 'Not authorised'
}.get(result_code, 'Unknown reason'))
mqttc.disconnect()
return
self.topics = {key: value for key, value in self.topics.items()
if value is None}
old_topics = userdata['topics']
for topic, qos in old_topics.items():
# qos is None if we were in process of subscribing
if qos is not None:
self.subscribe(topic, qos)
userdata['topics'] = {}
userdata['progress'] = {}
def _mqtt_on_subscribe(self, _mqttc, _userdata, mid, granted_qos):
"""Subscribe successful callback."""
topic = self.progress.pop(mid, None)
if topic is None:
return
self.topics[topic] = granted_qos[0]
for topic, qos in old_topics.items():
# qos is None if we were in process of subscribing
if qos is not None:
mqttc.subscribe(topic, qos)
def _mqtt_on_message(self, _mqttc, _userdata, msg):
"""Message received callback."""
self.hass.bus.fire(EVENT_MQTT_MESSAGE_RECEIVED, {
ATTR_TOPIC: msg.topic,
ATTR_QOS: msg.qos,
ATTR_PAYLOAD: msg.payload.decode('utf-8'),
})
def _mqtt_on_unsubscribe(self, _mqttc, _userdata, mid, granted_qos):
"""Unsubscribe successful callback."""
topic = self.progress.pop(mid, None)
if topic is None:
return
self.topics.pop(topic, None)
def _mqtt_on_subscribe(mqttc, userdata, mid, granted_qos):
""" Called when subscribe successful. """
topic = userdata['progress'].pop(mid, None)
if topic is None:
return
userdata['topics'][topic] = granted_qos
def _mqtt_on_disconnect(self, _mqttc, _userdata, result_code):
"""Disconnected callback."""
self.progress = {}
self.topics = {key: value for key, value in self.topics.items()
if value is not None}
# Remove None values from topic list
for key in list(self.topics):
if self.topics[key] is None:
self.topics.pop(key)
def _mqtt_on_unsubscribe(mqttc, userdata, mid, granted_qos):
""" Called when subscribe successful. """
topic = userdata['progress'].pop(mid, None)
if topic is None:
return
userdata['topics'].pop(topic, None)
# When disconnected because of calling disconnect()
if result_code == 0:
return
tries = 0
wait_time = 0
def _mqtt_on_disconnect(mqttc, userdata, result_code):
""" Called when being disconnected. """
# When disconnected because of calling disconnect()
if result_code == 0:
return
while True:
try:
if self._mqttc.reconnect() == 0:
_LOGGER.info('Successfully reconnected to the MQTT server')
break
except socket.error:
pass
tries = 0
wait_time = 0
while True:
try:
if mqttc.reconnect() == 0:
_LOGGER.info('Successfully reconnected to the MQTT server')
break
except socket.error:
pass
wait_time = min(2**tries, MAX_RECONNECT_WAIT)
_LOGGER.warning(
'Disconnected from MQTT (%s). Trying to reconnect in %ss',
result_code, wait_time)
# It is ok to sleep here as we are in the MQTT thread.
time.sleep(wait_time)
tries += 1
wait_time = min(2**tries, MAX_RECONNECT_WAIT)
_LOGGER.warning(
'Disconnected from MQTT (%s). Trying to reconnect in %ss',
result_code, wait_time)
# It is ok to sleep here as we are in the MQTT thread.
time.sleep(wait_time)
tries += 1
def _raise_on_error(result):
""" Raise error if error result. """
"""Raise error if error result."""
if result != 0:
raise HomeAssistantError('Error talking to MQTT: {}'.format(result))
def _match_topic(subscription, topic):
""" Returns if topic matches subscription. """
"""Test if topic matches subscription."""
if subscription.endswith('#'):
return (subscription[:-2] == topic or
topic.startswith(subscription[:-1]))

View File

@@ -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

View File

@@ -0,0 +1,105 @@
"""
homeassistant.components.mqtt_eventstream
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connect two Home Assistant instances via MQTT..
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mqtt_eventstream.html
"""
import json
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,
EVENT_SERVICE_EXECUTED,
EVENT_STATE_CHANGED,
)
import homeassistant.loader as loader
from homeassistant.remote import JSONEncoder
# The domain of your component. Should be equal to the name of your component
DOMAIN = "mqtt_eventstream"
# List of component names (string) your component depends upon
DEPENDENCIES = ['mqtt']
def setup(hass, config):
""" Setup th MQTT eventstream component. """
mqtt = loader.get_component('mqtt')
pub_topic = config[DOMAIN].get('publish_topic', None)
sub_topic = config[DOMAIN].get('subscribe_topic', None)
def _event_publisher(event):
""" Handle events by publishing them on the MQTT queue. """
if event.origin != EventOrigin.local:
return
if event.event_type == EVENT_TIME_CHANGED:
return
# Filter out the events that were triggered by publishing
# to the MQTT topic, or you will end up in an infinite loop.
if event.event_type == EVENT_CALL_SERVICE:
if (
event.data.get('domain') == MQTT_DOMAIN and
event.data.get('service') == MQTT_SVC_PUBLISH and
event.data[ATTR_SERVICE_DATA].get('topic') == pub_topic
):
return
# Filter out all the "event service executed" events because they
# are only used internally by core as callbacks for blocking
# during the interval while a service is being executed.
# They will serve no purpose to the external system,
# and thus are unnecessary traffic.
# And at any rate it would cause an infinite loop to publish them
# because publishing to an MQTT topic itself triggers one.
if event.event_type == EVENT_SERVICE_EXECUTED:
return
event_info = {'event_type': event.event_type, 'event_data': event.data}
msg = json.dumps(event_info, cls=JSONEncoder)
mqtt.publish(hass, pub_topic, msg)
# Only listen for local events if you are going to publish them
if pub_topic:
hass.bus.listen(MATCH_ALL, _event_publisher)
# Process events from a remote server that are received on a queue
def _event_receiver(topic, payload, qos):
"""
Receive events published by the other HA instance and fire
them on this hass instance.
"""
event = json.loads(payload)
event_type = event.get('event_type')
event_data = event.get('event_data')
# Special case handling for event STATE_CHANGED
# We will try to convert state dicts back to State objects
# Copied over from the _handle_api_post_events_event method
# of the api component.
if event_type == EVENT_STATE_CHANGED and event_data:
for key in ('old_state', 'new_state'):
state = State.from_dict(event_data.get(key))
if state:
event_data[key] = state
hass.bus.fire(
event_type,
event_data=event_data,
origin=EventOrigin.remote
)
# Only subscribe if you specified a topic
if sub_topic:
mqtt.subscribe(hass, sub_topic, _event_receiver)
hass.states.set('{domain}.initialized'.format(domain=DOMAIN), True)
# return boolean to indicate that initialization was successful
return True

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