Merge pull request #1539 from home-assistant/dev

Release 203
This commit is contained in:
Pascal Vizeli 2020-02-28 10:56:22 +01:00 committed by GitHub
commit b9496e0972
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 747 additions and 144 deletions

22
API.md
View File

@ -863,6 +863,19 @@ return:
"version": "1",
"latest_version": "2",
"audio": {
"card": [
{
"name": "...",
"driver": "...",
"profiles": [
{
"name": "...",
"description": "...",
"active": false
}
]
}
],
"input": [
{
"name": "...",
@ -931,6 +944,15 @@ return:
}
```
- POST `/audio/profile`
```json
{
"card": "...",
"name": "..."
}
```
- GET `/audio/stats`
```json

View File

@ -10,7 +10,7 @@ gitpython==3.1.0
jinja2==2.11.1
packaging==20.1
ptvsd==4.3.2
pulsectl==20.2.2
pulsectl==20.2.4
pytz==2019.3
pyudev==0.22.0
ruamel.yaml==0.15.100

View File

@ -117,8 +117,11 @@ function init_dbus() {
mkdir -p /var/lib/dbus
cp -f /etc/machine-id /var/lib/dbus/machine-id
# run
# cleanups
mkdir -p /run/dbus
rm -f /run/dbus/pid
# run
dbus-daemon --system --print-address
}

View File

@ -329,6 +329,7 @@ class RestAPI(CoreSysAttributes):
web.post("/audio/update", api_audio.update),
web.post("/audio/restart", api_audio.restart),
web.post("/audio/reload", api_audio.reload),
web.post("/audio/profile", api_audio.set_profile),
web.post("/audio/volume/{source}", api_audio.set_volume),
web.post("/audio/default/{source}", api_audio.set_default),
]

View File

@ -11,6 +11,7 @@ from ..const import (
ATTR_AUDIO,
ATTR_BLK_READ,
ATTR_BLK_WRITE,
ATTR_CARD,
ATTR_CPU_PERCENT,
ATTR_HOST,
ATTR_INPUT,
@ -28,7 +29,7 @@ from ..const import (
)
from ..coresys import CoreSysAttributes
from ..exceptions import APIError
from ..host.sound import SourceType
from ..host.sound import StreamType
from .utils import api_process, api_process_raw, api_validate
_LOGGER: logging.Logger = logging.getLogger(__name__)
@ -44,6 +45,10 @@ SCHEMA_VOLUME = vol.Schema(
SCHEMA_DEFAULT = vol.Schema({vol.Required(ATTR_NAME): vol.Coerce(str)})
SCHEMA_PROFILE = vol.Schema(
{vol.Required(ATTR_CARD): vol.Coerce(str), vol.Required(ATTR_NAME): vol.Coerce(str)}
)
class APIAudio(CoreSysAttributes):
"""Handle RESTful API for Audio functions."""
@ -56,13 +61,12 @@ class APIAudio(CoreSysAttributes):
ATTR_LATEST_VERSION: self.sys_audio.latest_version,
ATTR_HOST: str(self.sys_docker.network.audio),
ATTR_AUDIO: {
ATTR_CARD: [attr.asdict(card) for card in self.sys_host.sound.cards],
ATTR_INPUT: [
attr.asdict(profile)
for profile in self.sys_host.sound.input_profiles
attr.asdict(stream) for stream in self.sys_host.sound.inputs
],
ATTR_OUTPUT: [
attr.asdict(profile)
for profile in self.sys_host.sound.output_profiles
attr.asdict(stream) for stream in self.sys_host.sound.outputs
],
},
}
@ -110,8 +114,8 @@ class APIAudio(CoreSysAttributes):
@api_process
async def set_volume(self, request: web.Request) -> None:
"""Set Audio information."""
source: SourceType = SourceType(request.match_info.get("source"))
"""Set audio volume on stream."""
source: StreamType = StreamType(request.match_info.get("source"))
body = await api_validate(SCHEMA_VOLUME, request)
await asyncio.shield(
@ -120,8 +124,17 @@ class APIAudio(CoreSysAttributes):
@api_process
async def set_default(self, request: web.Request) -> None:
"""Set Audio default sources."""
source: SourceType = SourceType(request.match_info.get("source"))
"""Set audio default stream."""
source: StreamType = StreamType(request.match_info.get("source"))
body = await api_validate(SCHEMA_DEFAULT, request)
await asyncio.shield(self.sys_host.sound.set_default(source, body[ATTR_NAME]))
@api_process
async def set_profile(self, request: web.Request) -> None:
"""Set audio default sources."""
body = await api_validate(SCHEMA_DEFAULT, request)
await asyncio.shield(
self.sys_host.sound.set_profile(body[ATTR_CARD], body[ATTR_NAME])
)

View File

@ -42,11 +42,11 @@ class APIHardware(CoreSysAttributes):
ATTR_AUDIO: {
ATTR_INPUT: {
profile.name: profile.description
for profile in self.sys_host.sound.input_profiles
for profile in self.sys_host.sound.inputs
},
ATTR_OUTPUT: {
profile.name: profile.description
for profile in self.sys_host.sound.output_profiles
for profile in self.sys_host.sound.outputs
},
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
(self.webpackJsonp=self.webpackJsonp||[]).push([[2],{177:function(e,r,n){"use strict";n.r(r),n.d(r,"codeMirror",function(){return c}),n.d(r,"codeMirrorCss",function(){return i});var a=n(54),o=n.n(a),s=n(170),t=(n(171),n(172),n(11));o.a.commands.save=function(e){Object(t.a)(e.getWrapperElement(),"editor-save")};var c=o.a,i=s.a}}]);
//# sourceMappingURL=chunk.26756b56961f7bf94974.js.map

Binary file not shown.

View File

@ -0,0 +1 @@
{"version":3,"sources":["webpack:///./src/resources/codemirror.ts"],"names":["__webpack_require__","r","__webpack_exports__","d","codeMirror","codeMirrorCss","codemirror__WEBPACK_IMPORTED_MODULE_0__","codemirror__WEBPACK_IMPORTED_MODULE_0___default","n","codemirror_lib_codemirror_css__WEBPACK_IMPORTED_MODULE_1__","_common_dom_fire_event__WEBPACK_IMPORTED_MODULE_4__","_CodeMirror","commands","save","cm","fireEvent","getWrapperElement","_codeMirrorCss"],"mappings":"sFAAAA,EAAAC,EAAAC,GAAAF,EAAAG,EAAAD,EAAA,+BAAAE,IAAAJ,EAAAG,EAAAD,EAAA,kCAAAG,IAAA,IAAAC,EAAAN,EAAA,IAAAO,EAAAP,EAAAQ,EAAAF,GAAAG,EAAAT,EAAA,KAAAU,GAAAV,EAAA,KAAAA,EAAA,KAAAA,EAAA,KAQAW,IAAYC,SAASC,KAAO,SAACC,GAC3BC,YAAUD,EAAGE,oBAAqB,gBAE7B,IAAMZ,EAAkBO,IAClBN,EAAqBY","file":"chunk.26756b56961f7bf94974.js","sourcesContent":["// @ts-ignore\nimport _CodeMirror, { Editor } from \"codemirror\";\n// @ts-ignore\nimport _codeMirrorCss from \"codemirror/lib/codemirror.css\";\nimport \"codemirror/mode/yaml/yaml\";\nimport \"codemirror/mode/jinja2/jinja2\";\nimport { fireEvent } from \"../common/dom/fire_event\";\n\n_CodeMirror.commands.save = (cm: Editor) => {\n fireEvent(cm.getWrapperElement(), \"editor-save\");\n};\nexport const codeMirror: any = _CodeMirror;\nexport const codeMirrorCss: any = _codeMirrorCss;\n"],"sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
/**
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
part of the polymer project is also subject to an additional IP rights grant
found at http://polymer.github.io/PATENTS.txt
*/

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1 @@
{"version":3,"sources":["webpack:///./hassio/src/ingress-view/hassio-ingress-view.ts"],"names":["customElement","HassioIngressView","property","this","_addon","html","_templateObject2","name","ingress_url","_templateObject","changedProps","_get","_getPrototypeOf","prototype","call","has","addon","route","path","substr","oldRoute","get","oldAddon","undefined","_fetchData","_callee","addonSlug","_ref","_ref2","regeneratorRuntime","wrap","_context","prev","next","Promise","all","fetchHassioAddonInfo","hass","Error","createHassioSession","sent","_slicedToArray","ingress","t0","console","error","alert","message","history","back","stop","css","_templateObject3","LitElement"],"mappings":"snSAmBCA,YAAc,0CACTC,smBACHC,kEACAA,mEACAA,4EAED,WACE,OAAKC,KAAKC,OAMHC,YAAPC,IAC0BH,KAAKC,OAAOG,KACpBJ,KAAKC,OAAOI,aAPrBH,YAAPI,0CAYJ,SAAkBC,GAGhB,GAFAC,EAAAC,EApBEX,EAoBFY,WAAA,eAAAV,MAAAW,KAAAX,KAAmBO,GAEdA,EAAaK,IAAI,SAAtB,CAIA,IAAMC,EAAQb,KAAKc,MAAMC,KAAKC,OAAO,GAE/BC,EAAWV,EAAaW,IAAI,SAC5BC,EAAWF,EAAWA,EAASF,KAAKC,OAAO,QAAKI,EAElDP,GAASA,IAAUM,GACrBnB,KAAKqB,WAAWR,0FAIpB,SAAAS,EAAyBC,GAAzB,IAAAC,EAAAC,EAAAZ,EAAA,OAAAa,mBAAAC,KAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAC,KAAA,EAAAD,EAAAE,KAAA,EAE0BC,QAAQC,IAAI,CAChCC,YAAqBjC,KAAKkC,KAAMX,GAAhC,MAAiD,WAC/C,MAAM,IAAIY,MAAM,iCAElBC,YAAoBpC,KAAKkC,MAAzB,MAAqC,WACnC,MAAM,IAAIC,MAAM,2CAPxB,UAAAX,EAAAI,EAAAS,KAAAZ,EAAAa,EAAAd,EAAA,IAEWX,EAFXY,EAAA,IAWec,QAXf,CAAAX,EAAAE,KAAA,cAYY,IAAIK,MAAM,wCAZtB,OAeInC,KAAKC,OAASY,EAflBe,EAAAE,KAAA,iBAAAF,EAAAC,KAAA,GAAAD,EAAAY,GAAAZ,EAAA,SAkBIa,QAAQC,MAARd,EAAAY,IACAG,MAAMf,EAAAY,GAAII,SAAW,mCACrBC,QAAQC,OApBZ,yBAAAlB,EAAAmB,SAAAzB,EAAAtB,KAAA,yRAwBA,WACE,OAAOgD,YAAPC,UA7D4BC","file":"chunk.35929da61d769e57c884.js","sourcesContent":["import {\n LitElement,\n customElement,\n property,\n TemplateResult,\n html,\n PropertyValues,\n CSSResult,\n css,\n} from \"lit-element\";\nimport { HomeAssistant, Route } from \"../../../src/types\";\nimport { createHassioSession } from \"../../../src/data/hassio/supervisor\";\nimport {\n HassioAddonDetails,\n fetchHassioAddonInfo,\n} from \"../../../src/data/hassio/addon\";\nimport \"../../../src/layouts/hass-loading-screen\";\nimport \"../../../src/layouts/hass-subpage\";\n\n@customElement(\"hassio-ingress-view\")\nclass HassioIngressView extends LitElement {\n @property() public hass!: HomeAssistant;\n @property() public route!: Route;\n @property() private _addon?: HassioAddonDetails;\n\n protected render(): TemplateResult {\n if (!this._addon) {\n return html`\n <hass-loading-screen></hass-loading-screen>\n `;\n }\n\n return html`\n <hass-subpage .header=${this._addon.name} hassio>\n <iframe src=${this._addon.ingress_url}></iframe>\n </hass-subpage>\n `;\n }\n\n protected updated(changedProps: PropertyValues) {\n super.firstUpdated(changedProps);\n\n if (!changedProps.has(\"route\")) {\n return;\n }\n\n const addon = this.route.path.substr(1);\n\n const oldRoute = changedProps.get(\"route\") as this[\"route\"] | undefined;\n const oldAddon = oldRoute ? oldRoute.path.substr(1) : undefined;\n\n if (addon && addon !== oldAddon) {\n this._fetchData(addon);\n }\n }\n\n private async _fetchData(addonSlug: string) {\n try {\n const [addon] = await Promise.all([\n fetchHassioAddonInfo(this.hass, addonSlug).catch(() => {\n throw new Error(\"Failed to fetch add-on info\");\n }),\n createHassioSession(this.hass).catch(() => {\n throw new Error(\"Failed to create an ingress session\");\n }),\n ]);\n\n if (!addon.ingress) {\n throw new Error(\"This add-on does not support ingress\");\n }\n\n this._addon = addon;\n } catch (err) {\n // tslint:disable-next-line\n console.error(err);\n alert(err.message || \"Unknown error starting ingress.\");\n history.back();\n }\n }\n\n static get styles(): CSSResult {\n return css`\n iframe {\n display: block;\n width: 100%;\n height: 100%;\n border: 0;\n }\n paper-icon-button {\n color: var(--text-primary-color);\n }\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"hassio-ingress-view\": HassioIngressView;\n }\n}\n"],"sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,189 @@
/**
* @license
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
/**
@license
Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
part of the polymer project is also subject to an additional IP rights grant
found at http://polymer.github.io/PATENTS.txt
*/
/**
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
part of the polymer project is also subject to an additional IP rights grant
found at http://polymer.github.io/PATENTS.txt
*/
/**
@license
Copyright 2018 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
/**
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
part of the polymer project is also subject to an additional IP rights grant
found at http://polymer.github.io/PATENTS.txt
*/
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/**
* @license
* Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
/**
* @license
* Copyright 2019 Google Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* @license
* Copyright 2016 Google Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
part of the polymer project is also subject to an additional IP rights grant
found at http://polymer.github.io/PATENTS.txt
*/
/**
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
/**
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
part of the polymer project is also subject to an additional IP rights grant
found at http://polymer.github.io/PATENTS.txt
*/
/*!
* Fuse.js v3.4.4 - Lightweight fuzzy-search (http://fusejs.io)
*
* Copyright (c) 2012-2017 Kirollos Risk (http://kiro.me)
* All Rights Reserved. Apache Software License 2.0
*
* http://www.apache.org/licenses/LICENSE-2.0
*/

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,20 @@
/**
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
/**
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
part of the polymer project is also subject to an additional IP rights grant
found at http://polymer.github.io/PATENTS.txt
*/

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,28 @@
/**
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
part of the polymer project is also subject to an additional IP rights grant
found at http://polymer.github.io/PATENTS.txt
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
* @license MIT
*/
/**
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
part of the polymer project is also subject to an additional IP rights grant
found at http://polymer.github.io/PATENTS.txt
*/

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
/**
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
part of the polymer project is also subject to an additional IP rights grant
found at http://polymer.github.io/PATENTS.txt
*/

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,16 @@
/**
@license
Copyright 2018 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
/**
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
part of the polymer project is also subject to an additional IP rights grant
found at http://polymer.github.io/PATENTS.txt
*/

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
!function(e){function n(n){for(var t,o,a=n[0],i=n[1],c=0,d=[];c<a.length;c++)o=a[c],Object.prototype.hasOwnProperty.call(r,o)&&r[o]&&d.push(r[o][0]),r[o]=0;for(t in i)Object.prototype.hasOwnProperty.call(i,t)&&(e[t]=i[t]);for(u&&u(n);d.length;)d.shift()()}var t={},r={6:0};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.e=function(e){var n=[],t=r[e];if(0!==t)if(t)n.push(t[2]);else{var a=new Promise(function(n,o){t=r[e]=[n,o]});n.push(t[2]=a);var i,c=document.createElement("script");c.charset="utf-8",c.timeout=120,o.nc&&c.setAttribute("nonce",o.nc),c.src=function(e){return o.p+"chunk."+{0:"87b1d37fc9b8a6f7e2a6",1:"e46c606dd9100816af4e",2:"92a11ac1b80e0d7839d2",3:"429840c83fad61bc51a8",4:"715824f4764bdbe425b1",5:"9d371c8143226d4eaaee",7:"43e40fd69686ad51301d",8:"0b82745c7bdffe5c1404",9:"990ee58006b248f55d23",10:"4d45ee0a3d852768f97e",11:"b60200a57d6f63941b30",12:"b2dce600432c76a53d8c",13:"8527374a266cecf93aa9",14:"f49e500cf58ea310d452",15:"d4931d72592ad48ba2be"}[e]+".js"}(e);var u=new Error;i=function(n){c.onerror=c.onload=null,clearTimeout(d);var t=r[e];if(0!==t){if(t){var o=n&&("load"===n.type?"missing":n.type),a=n&&n.target&&n.target.src;u.message="Loading chunk "+e+" failed.\n("+o+": "+a+")",u.name="ChunkLoadError",u.type=o,u.request=a,t[1](u)}r[e]=void 0}};var d=setTimeout(function(){i({type:"timeout",target:c})},12e4);c.onerror=c.onload=i,document.head.appendChild(c)}return Promise.all(n)},o.m=e,o.c=t,o.d=function(e,n,t){o.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,n){if(1&n&&(e=o(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(o.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)o.d(t,r,function(n){return e[n]}.bind(null,r));return t},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,"a",n),n},o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.p="/api/hassio/app/",o.oe=function(e){throw console.error(e),e};var a=self.webpackJsonp=self.webpackJsonp||[],i=a.push.bind(a);a.push=n,a=a.slice();for(var c=0;c<a.length;c++)n(a[c]);var u=i;o(o.s=0)}([function(e,n,t){window.loadES5Adapter().then(function(){t.e(12).then(t.t.bind(null,1,7)),Promise.all([t.e(1),t.e(8)]).then(t.bind(null,3)),Promise.all([t.e(1),t.e(15),t.e(10)]).then(t.bind(null,2))});var r=document.createElement("style");r.innerHTML="\nbody {\n font-family: Roboto, sans-serif;\n -moz-osx-font-smoothing: grayscale;\n -webkit-font-smoothing: antialiased;\n font-weight: 400;\n margin: 0;\n padding: 0;\n height: 100vh;\n}\n",document.head.appendChild(r)}]);
!function(e){function n(n){for(var t,o,a=n[0],i=n[1],c=0,f=[];c<a.length;c++)o=a[c],Object.prototype.hasOwnProperty.call(r,o)&&r[o]&&f.push(r[o][0]),r[o]=0;for(t in i)Object.prototype.hasOwnProperty.call(i,t)&&(e[t]=i[t]);for(u&&u(n);f.length;)f.shift()()}var t={},r={6:0};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.e=function(e){var n=[],t=r[e];if(0!==t)if(t)n.push(t[2]);else{var a=new Promise(function(n,o){t=r[e]=[n,o]});n.push(t[2]=a);var i,c=document.createElement("script");c.charset="utf-8",c.timeout=120,o.nc&&c.setAttribute("nonce",o.nc),c.src=function(e){return o.p+"chunk."+{0:"b6e61f8340c32e6904ca",1:"9339f70c8bfe2cbef5ad",2:"26756b56961f7bf94974",3:"b2a892416a728ca06e9a",4:"26be881fcb628958e718",5:"ea2041e4c67d4c05b0dd",7:"1a25d23325fed5a4d90b",8:"af76a4db9eb1e2862aae",9:"35929da61d769e57c884",10:"5e32280d595be3742226",11:"a9cf4ae83af78188e158",12:"b2dce600432c76a53d8c",13:"70a435e100109291f210",14:"93a8a2e1dbccae0e07fa",15:"541d0b76b660d8646074"}[e]+".js"}(e);var u=new Error;i=function(n){c.onerror=c.onload=null,clearTimeout(f);var t=r[e];if(0!==t){if(t){var o=n&&("load"===n.type?"missing":n.type),a=n&&n.target&&n.target.src;u.message="Loading chunk "+e+" failed.\n("+o+": "+a+")",u.name="ChunkLoadError",u.type=o,u.request=a,t[1](u)}r[e]=void 0}};var f=setTimeout(function(){i({type:"timeout",target:c})},12e4);c.onerror=c.onload=i,document.head.appendChild(c)}return Promise.all(n)},o.m=e,o.c=t,o.d=function(e,n,t){o.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,n){if(1&n&&(e=o(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(o.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)o.d(t,r,function(n){return e[n]}.bind(null,r));return t},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,"a",n),n},o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.p="/api/hassio/app/",o.oe=function(e){throw console.error(e),e};var a=self.webpackJsonp=self.webpackJsonp||[],i=a.push.bind(a);a.push=n,a=a.slice();for(var c=0;c<a.length;c++)n(a[c]);var u=i;o(o.s=0)}([function(e,n,t){window.loadES5Adapter().then(function(){t.e(12).then(t.t.bind(null,1,7)),Promise.all([t.e(1),t.e(8)]).then(t.bind(null,3)),Promise.all([t.e(1),t.e(15),t.e(10)]).then(t.bind(null,2))});var r=document.createElement("style");r.innerHTML="\nbody {\n font-family: Roboto, sans-serif;\n -moz-osx-font-smoothing: grayscale;\n -webkit-font-smoothing: antialiased;\n font-weight: 400;\n margin: 0;\n padding: 0;\n height: 100vh;\n}\n",document.head.appendChild(r)}]);
//# sourceMappingURL=entrypoint.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,43 +1,43 @@
{
"vendors~confirmation~hassio-addon-view.js": "/api/hassio/app/chunk.87b1d37fc9b8a6f7e2a6.js",
"vendors~confirmation~hassio-addon-view.js.map": "/api/hassio/app/chunk.87b1d37fc9b8a6f7e2a6.js.map",
"vendors~hassio-icons~hassio-main.js": "/api/hassio/app/chunk.e46c606dd9100816af4e.js",
"vendors~hassio-icons~hassio-main.js.map": "/api/hassio/app/chunk.e46c606dd9100816af4e.js.map",
"codemirror.js": "/api/hassio/app/chunk.92a11ac1b80e0d7839d2.js",
"codemirror.js.map": "/api/hassio/app/chunk.92a11ac1b80e0d7839d2.js.map",
"confirmation.js": "/api/hassio/app/chunk.429840c83fad61bc51a8.js",
"confirmation.js.map": "/api/hassio/app/chunk.429840c83fad61bc51a8.js.map",
"dialog-hassio-markdown.js": "/api/hassio/app/chunk.715824f4764bdbe425b1.js",
"dialog-hassio-markdown.js.map": "/api/hassio/app/chunk.715824f4764bdbe425b1.js.map",
"dialog-hassio-snapshot.js": "/api/hassio/app/chunk.9d371c8143226d4eaaee.js",
"dialog-hassio-snapshot.js.map": "/api/hassio/app/chunk.9d371c8143226d4eaaee.js.map",
"vendors~confirmation~hassio-addon-view.js": "/api/hassio/app/chunk.b6e61f8340c32e6904ca.js",
"vendors~confirmation~hassio-addon-view.js.map": "/api/hassio/app/chunk.b6e61f8340c32e6904ca.js.map",
"vendors~hassio-icons~hassio-main.js": "/api/hassio/app/chunk.9339f70c8bfe2cbef5ad.js",
"vendors~hassio-icons~hassio-main.js.map": "/api/hassio/app/chunk.9339f70c8bfe2cbef5ad.js.map",
"codemirror.js": "/api/hassio/app/chunk.26756b56961f7bf94974.js",
"codemirror.js.map": "/api/hassio/app/chunk.26756b56961f7bf94974.js.map",
"confirmation.js": "/api/hassio/app/chunk.b2a892416a728ca06e9a.js",
"confirmation.js.map": "/api/hassio/app/chunk.b2a892416a728ca06e9a.js.map",
"dialog-hassio-markdown.js": "/api/hassio/app/chunk.26be881fcb628958e718.js",
"dialog-hassio-markdown.js.map": "/api/hassio/app/chunk.26be881fcb628958e718.js.map",
"dialog-hassio-snapshot.js": "/api/hassio/app/chunk.ea2041e4c67d4c05b0dd.js",
"dialog-hassio-snapshot.js.map": "/api/hassio/app/chunk.ea2041e4c67d4c05b0dd.js.map",
"entrypoint.js": "/api/hassio/app/entrypoint.js",
"entrypoint.js.map": "/api/hassio/app/entrypoint.js.map",
"hassio-addon-view.js": "/api/hassio/app/chunk.43e40fd69686ad51301d.js",
"hassio-addon-view.js.map": "/api/hassio/app/chunk.43e40fd69686ad51301d.js.map",
"hassio-icons.js": "/api/hassio/app/chunk.0b82745c7bdffe5c1404.js",
"hassio-icons.js.map": "/api/hassio/app/chunk.0b82745c7bdffe5c1404.js.map",
"hassio-ingress-view.js": "/api/hassio/app/chunk.990ee58006b248f55d23.js",
"hassio-ingress-view.js.map": "/api/hassio/app/chunk.990ee58006b248f55d23.js.map",
"hassio-main.js": "/api/hassio/app/chunk.4d45ee0a3d852768f97e.js",
"hassio-main.js.map": "/api/hassio/app/chunk.4d45ee0a3d852768f97e.js.map",
"mdi-icons.js": "/api/hassio/app/chunk.b60200a57d6f63941b30.js",
"mdi-icons.js.map": "/api/hassio/app/chunk.b60200a57d6f63941b30.js.map",
"hassio-addon-view.js": "/api/hassio/app/chunk.1a25d23325fed5a4d90b.js",
"hassio-addon-view.js.map": "/api/hassio/app/chunk.1a25d23325fed5a4d90b.js.map",
"hassio-icons.js": "/api/hassio/app/chunk.af76a4db9eb1e2862aae.js",
"hassio-icons.js.map": "/api/hassio/app/chunk.af76a4db9eb1e2862aae.js.map",
"hassio-ingress-view.js": "/api/hassio/app/chunk.35929da61d769e57c884.js",
"hassio-ingress-view.js.map": "/api/hassio/app/chunk.35929da61d769e57c884.js.map",
"hassio-main.js": "/api/hassio/app/chunk.5e32280d595be3742226.js",
"hassio-main.js.map": "/api/hassio/app/chunk.5e32280d595be3742226.js.map",
"mdi-icons.js": "/api/hassio/app/chunk.a9cf4ae83af78188e158.js",
"mdi-icons.js.map": "/api/hassio/app/chunk.a9cf4ae83af78188e158.js.map",
"roboto.js": "/api/hassio/app/chunk.b2dce600432c76a53d8c.js",
"roboto.js.map": "/api/hassio/app/chunk.b2dce600432c76a53d8c.js.map",
"vendors~codemirror.js": "/api/hassio/app/chunk.8527374a266cecf93aa9.js",
"vendors~codemirror.js.map": "/api/hassio/app/chunk.8527374a266cecf93aa9.js.map",
"vendors~hassio-addon-view.js": "/api/hassio/app/chunk.f49e500cf58ea310d452.js",
"vendors~hassio-addon-view.js.map": "/api/hassio/app/chunk.f49e500cf58ea310d452.js.map",
"vendors~hassio-main.js": "/api/hassio/app/chunk.d4931d72592ad48ba2be.js",
"vendors~hassio-main.js.map": "/api/hassio/app/chunk.d4931d72592ad48ba2be.js.map",
"201359fd5a526afe13ef.worker.js": "/api/hassio/app/201359fd5a526afe13ef.worker.js",
"201359fd5a526afe13ef.worker.js.map": "/api/hassio/app/201359fd5a526afe13ef.worker.js.map",
"chunk.429840c83fad61bc51a8.js.LICENSE": "/api/hassio/app/chunk.429840c83fad61bc51a8.js.LICENSE",
"chunk.715824f4764bdbe425b1.js.LICENSE": "/api/hassio/app/chunk.715824f4764bdbe425b1.js.LICENSE",
"chunk.87b1d37fc9b8a6f7e2a6.js.LICENSE": "/api/hassio/app/chunk.87b1d37fc9b8a6f7e2a6.js.LICENSE",
"chunk.9d371c8143226d4eaaee.js.LICENSE": "/api/hassio/app/chunk.9d371c8143226d4eaaee.js.LICENSE",
"chunk.d4931d72592ad48ba2be.js.LICENSE": "/api/hassio/app/chunk.d4931d72592ad48ba2be.js.LICENSE",
"chunk.e46c606dd9100816af4e.js.LICENSE": "/api/hassio/app/chunk.e46c606dd9100816af4e.js.LICENSE",
"chunk.f49e500cf58ea310d452.js.LICENSE": "/api/hassio/app/chunk.f49e500cf58ea310d452.js.LICENSE"
"vendors~codemirror.js": "/api/hassio/app/chunk.70a435e100109291f210.js",
"vendors~codemirror.js.map": "/api/hassio/app/chunk.70a435e100109291f210.js.map",
"vendors~hassio-addon-view.js": "/api/hassio/app/chunk.93a8a2e1dbccae0e07fa.js",
"vendors~hassio-addon-view.js.map": "/api/hassio/app/chunk.93a8a2e1dbccae0e07fa.js.map",
"vendors~hassio-main.js": "/api/hassio/app/chunk.541d0b76b660d8646074.js",
"vendors~hassio-main.js.map": "/api/hassio/app/chunk.541d0b76b660d8646074.js.map",
"a1ebfa0a88593a3b571c.worker.js": "/api/hassio/app/a1ebfa0a88593a3b571c.worker.js",
"a1ebfa0a88593a3b571c.worker.js.map": "/api/hassio/app/a1ebfa0a88593a3b571c.worker.js.map",
"chunk.26be881fcb628958e718.js.LICENSE": "/api/hassio/app/chunk.26be881fcb628958e718.js.LICENSE",
"chunk.541d0b76b660d8646074.js.LICENSE": "/api/hassio/app/chunk.541d0b76b660d8646074.js.LICENSE",
"chunk.9339f70c8bfe2cbef5ad.js.LICENSE": "/api/hassio/app/chunk.9339f70c8bfe2cbef5ad.js.LICENSE",
"chunk.93a8a2e1dbccae0e07fa.js.LICENSE": "/api/hassio/app/chunk.93a8a2e1dbccae0e07fa.js.LICENSE",
"chunk.b2a892416a728ca06e9a.js.LICENSE": "/api/hassio/app/chunk.b2a892416a728ca06e9a.js.LICENSE",
"chunk.b6e61f8340c32e6904ca.js.LICENSE": "/api/hassio/app/chunk.b6e61f8340c32e6904ca.js.LICENSE",
"chunk.ea2041e4c67d4c05b0dd.js.LICENSE": "/api/hassio/app/chunk.ea2041e4c67d4c05b0dd.js.LICENSE"
}

View File

@ -4,6 +4,7 @@ from contextlib import suppress
import logging
from pathlib import Path
from typing import Awaitable, Optional
import shutil
import jinja2
@ -18,6 +19,7 @@ from .validate import SCHEMA_AUDIO_CONFIG
_LOGGER: logging.Logger = logging.getLogger(__name__)
PULSE_CLIENT_TMPL: Path = Path(__file__).parents[0].joinpath("data/pulse-client.tmpl")
ASOUND_TMPL: Path = Path(__file__).parents[0].joinpath("data/asound.tmpl")
class Audio(JsonConfig, CoreSysAttributes):
@ -31,9 +33,14 @@ class Audio(JsonConfig, CoreSysAttributes):
self.client_template: Optional[jinja2.Template] = None
@property
def path_extern_data(self) -> Path:
"""Return path of pulse cookie file."""
return self.sys_config.path_extern_audio.joinpath("external")
def path_extern_pulse(self) -> Path:
"""Return path of pulse socket file."""
return self.sys_config.path_extern_audio.joinpath("external/pulse.sock")
@property
def path_extern_asound(self) -> Path:
"""Return path of default asound config file."""
return self.sys_config.path_extern_audio.joinpath("asound")
@property
def version(self) -> Optional[str]:
@ -81,9 +88,7 @@ class Audio(JsonConfig, CoreSysAttributes):
# Run PulseAudio
with suppress(AudioError):
if await self.instance.is_running():
await self.restart()
else:
if not await self.instance.is_running():
await self.start()
# Initialize Client Template
@ -92,6 +97,14 @@ class Audio(JsonConfig, CoreSysAttributes):
except OSError as err:
_LOGGER.error("Can't read pulse-client.tmpl: %s", err)
# Setup default asound config
asound = self.sys_config.path_audio.joinpath("asound")
if not asound.exists():
try:
shutil.copy(ASOUND_TMPL, asound)
except OSError as err:
_LOGGER.error("Can't create default asound: %s", err)
async def install(self) -> None:
"""Install Audio."""
_LOGGER.info("Setup Audio plugin")
@ -137,8 +150,12 @@ class Audio(JsonConfig, CoreSysAttributes):
async def restart(self) -> None:
"""Restart Audio plugin."""
with suppress(DockerAPIError):
_LOGGER.info("Restart Audio plugin")
try:
await self.instance.restart()
except DockerAPIError:
_LOGGER.error("Can't start Audio plugin")
raise AudioError() from None
async def start(self) -> None:
"""Run CoreDNS."""

View File

@ -21,6 +21,7 @@ from .dns import CoreDNS
from .hassos import HassOS
from .homeassistant import HomeAssistant
from .host import HostManager
from .hwmon import HwMonitor
from .ingress import Ingress
from .services import ServiceManager
from .snapshots import SnapshotManager
@ -57,6 +58,7 @@ async def initialize_coresys():
coresys.addons = AddonManager(coresys)
coresys.snapshots = SnapshotManager(coresys)
coresys.host = HostManager(coresys)
coresys.hwmonitor = HwMonitor(coresys)
coresys.ingress = Ingress(coresys)
coresys.tasks = Tasks(coresys)
coresys.services = ServiceManager(coresys)

View File

@ -3,7 +3,7 @@ from enum import Enum
from ipaddress import ip_network
from pathlib import Path
SUPERVISOR_VERSION = "202"
SUPERVISOR_VERSION = "203"
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
@ -233,6 +233,7 @@ ATTR_STAGE = "stage"
ATTR_CLI = "cli"
ATTR_DEFAULT = "default"
ATTR_VOLUME = "volume"
ATTR_CARD = "card"
PROVIDE_SERVICE = "provide"
NEED_SERVICE = "need"

View File

@ -142,13 +142,16 @@ class Core(CoreSysAttributes):
if self.sys_homeassistant.version == "landingpage":
self.sys_create_task(self.sys_homeassistant.install())
# Start observe the host Hardware
await self.sys_hwmonitor.load()
# Upate Host/Deivce information
self.sys_create_task(self.sys_host.reload())
self.sys_create_task(self.sys_updater.reload())
_LOGGER.info("Supervisor is up and running")
self.state = CoreStates.RUNNING
# On full host boot, relaod information
self.sys_create_task(self.sys_host.reload())
self.sys_create_task(self.sys_updater.reload())
async def stop(self):
"""Stop a running orchestration."""
# don't process scheduler anymore
@ -168,6 +171,7 @@ class Core(CoreSysAttributes):
self.sys_websession_ssl.close(),
self.sys_ingress.unload(),
self.sys_dns.unload(),
self.sys_hwmonitor.unload(),
]
)
except asyncio.TimeoutError:

View File

@ -22,6 +22,7 @@ if TYPE_CHECKING:
from .discovery import Discovery
from .dns import CoreDNS
from .hassos import HassOS
from .hwmon import HwMonitor
from .homeassistant import HomeAssistant
from .host import HostManager
from .ingress import Ingress
@ -76,6 +77,7 @@ class CoreSys:
self._secrets: Optional[SecretsManager] = None
self._store: Optional[StoreManager] = None
self._discovery: Optional[Discovery] = None
self._hwmonitor: Optional[HwMonitor] = None
@property
def machine(self) -> str:
@ -345,6 +347,18 @@ class CoreSys:
raise RuntimeError("HostManager already set!")
self._host = value
@property
def hwmonitor(self) -> HwMonitor:
"""Return HwMonitor object."""
return self._hwmonitor
@hwmonitor.setter
def hwmonitor(self, value: HwMonitor):
"""Set a HwMonitor object."""
if self._hwmonitor:
raise RuntimeError("HwMonitor already set!")
self._hwmonitor = value
@property
def ingress(self) -> Ingress:
"""Return Ingress object."""
@ -520,6 +534,11 @@ class CoreSysAttributes:
"""Return HostManager object."""
return self.coresys.host
@property
def sys_hwmonitor(self) -> HwMonitor:
"""Return HwMonitor object."""
return self.coresys.hwmonitor
@property
def sys_ingress(self) -> Ingress:
"""Return Ingress object."""

View File

@ -0,0 +1,13 @@
# Default to PulseAudio
pcm.!default {
type pulse
hint {
show on
description "Default ALSA Output (Home Assistant PulseAudio Sound Server)"
}
}
ctl.!default {
type pulse
}

View File

@ -308,10 +308,14 @@ class DockerAddon(DockerInterface):
"bind": "/etc/pulse/client.conf",
"mode": "ro",
},
str(self.sys_audio.path_extern_data.joinpath("pulse.sock")): {
str(self.sys_audio.path_extern_pulse): {
"bind": "/run/pulse.sock",
"mode": "rw",
},
str(self.sys_audio.path_extern_asound): {
"bind": "/etc/asound.conf",
"mode": "ro",
},
}
)

View File

@ -65,7 +65,7 @@ class DockerHomeAssistant(DockerInterface):
hostname=self.name,
detach=True,
privileged=True,
init=True,
init=False,
network_mode="host",
environment={
"HASSIO": self.sys_docker.network.supervisor,
@ -101,6 +101,7 @@ class DockerHomeAssistant(DockerInterface):
command=command,
privileged=True,
init=True,
entrypoint=[],
detach=True,
stdout=True,
stderr=True,

View File

@ -1,4 +1,5 @@
"""Pulse host control."""
from datetime import timedelta
from enum import Enum
import logging
from typing import List
@ -8,13 +9,14 @@ from pulsectl import Pulse, PulseError, PulseIndexError, PulseOperationFailed
from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import PulseAudioError
from ..utils import AsyncThrottle
_LOGGER: logging.Logger = logging.getLogger(__name__)
PULSE_NAME = "supervisor"
class SourceType(str, Enum):
class StreamType(str, Enum):
"""INPUT/OUTPUT type of source."""
INPUT = "input"
@ -22,7 +24,7 @@ class SourceType(str, Enum):
@attr.s(frozen=True)
class AudioProfile:
class AudioStream:
"""Represent a input/output profile."""
name: str = attr.ib()
@ -31,101 +33,186 @@ class AudioProfile:
default: bool = attr.ib()
@attr.s(frozen=True)
class SoundProfile:
"""Represent a Sound Card profile."""
name: str = attr.ib()
description: str = attr.ib()
active: bool = attr.ib()
@attr.s(frozen=True)
class SoundCard:
"""Represent a Sound Card."""
name: str = attr.ib()
driver: str = attr.ib()
profiles: List[SoundProfile] = attr.ib()
class SoundControl(CoreSysAttributes):
"""Pulse control from Host."""
def __init__(self, coresys: CoreSys) -> None:
"""Initialize PulseAudio sound control."""
self.coresys: CoreSys = coresys
self._input: List[AudioProfile] = []
self._output: List[AudioProfile] = []
self._cards: List[SoundCard] = []
self._inputs: List[AudioStream] = []
self._outputs: List[AudioStream] = []
@property
def input_profiles(self) -> List[AudioProfile]:
"""Return a list of available input profiles."""
return self._input
def cards(self) -> List[SoundCard]:
"""Return a list of available sound cards and profiles."""
return self._cards
@property
def output_profiles(self) -> List[AudioProfile]:
"""Return a list of available output profiles."""
return self._output
def inputs(self) -> List[AudioStream]:
"""Return a list of available input streams."""
return self._inputs
async def set_default(self, source: SourceType, name: str) -> None:
"""Set a profile to default input/output."""
try:
with Pulse(PULSE_NAME) as pulse:
if source == SourceType.OUTPUT:
# Get source and set it as default
source = pulse.get_source_by_name(name)
pulse.source_default_set(source)
else:
# Get sink and set it as default
sink = pulse.get_sink_by_name(name)
pulse.sink_default_set(sink)
except PulseIndexError:
_LOGGER.error("Can't find %s profile %s", source, name)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.error("Can't set %s as default: %s", name, err)
raise PulseAudioError() from None
@property
def outputs(self) -> List[AudioStream]:
"""Return a list of available output streams."""
return self._outputs
# Reload data
async def set_default(self, stream_type: StreamType, name: str) -> None:
"""Set a stream to default input/output."""
def _set_default():
try:
with Pulse(PULSE_NAME) as pulse:
if stream_type == StreamType.INPUT:
# Get source and set it as default
source = pulse.get_source_by_name(name)
pulse.source_default_set(source)
else:
# Get sink and set it as default
sink = pulse.get_sink_by_name(name)
pulse.sink_default_set(sink)
except PulseIndexError:
_LOGGER.error("Can't find %s profile %s", source, name)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.error("Can't set %s as default: %s", name, err)
raise PulseAudioError() from None
# Run and Reload data
await self.sys_run_in_executor(_set_default)
await self.update()
async def set_volume(self, source: SourceType, name: str, volume: float) -> None:
async def set_volume(
self, stream_type: StreamType, name: str, volume: float
) -> None:
"""Set a stream to volume input/output."""
def _set_volume():
try:
with Pulse(PULSE_NAME) as pulse:
if stream_type == StreamType.INPUT:
# Get source and set it as default
stream = pulse.get_source_by_name(name)
else:
# Get sink and set it as default
stream = pulse.get_sink_by_name(name)
pulse.volume_set_all_chans(stream, volume)
except PulseIndexError:
_LOGGER.error("Can't find %s profile %s", stream_type, name)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.error("Can't set %s volume: %s", name, err)
raise PulseAudioError() from None
# Run and Reload data
await self.sys_run_in_executor(_set_volume)
await self.update()
async def ativate_profile(self, card_name: str, profile_name: str) -> None:
"""Set a profile to volume input/output."""
try:
with Pulse(PULSE_NAME) as pulse:
if source == SourceType.OUTPUT:
# Get source and set it as default
source = pulse.get_source_by_name(name)
else:
# Get sink and set it as default
source = pulse.get_sink_by_name(name)
pulse.volume_set_all_chans(source, volume)
except PulseIndexError:
_LOGGER.error("Can't find %s profile %s", source, name)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.error("Can't set %s volume: %s", name, err)
raise PulseAudioError() from None
def _activate_profile():
try:
with Pulse(PULSE_NAME) as pulse:
card = pulse.get_sink_by_name(card_name)
pulse.card_profile_set(card, profile_name)
# Reload data
except PulseIndexError:
_LOGGER.error("Can't find %s profile %s", card_name, profile_name)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.error(
"Can't activate %s profile %s: %s", card_name, profile_name, err
)
raise PulseAudioError() from None
# Run and Reload data
await self.sys_run_in_executor(_activate_profile)
await self.update()
@AsyncThrottle(timedelta(seconds=10))
async def update(self):
"""Update properties over dbus."""
_LOGGER.info("Update PulseAudio information")
try:
with Pulse(PULSE_NAME) as pulse:
server = pulse.server_info()
# Update output
self._output.clear()
for sink in pulse.sink_list():
self._output.append(
AudioProfile(
sink.name,
sink.description,
sink.volume.value_flat,
sink.name == server.default_sink_name,
)
)
def _update():
try:
with Pulse(PULSE_NAME) as pulse:
server = pulse.server_info()
# Update input
self._input.clear()
for source in pulse.source_list():
self._input.append(
AudioProfile(
source.name,
source.description,
source.volume.value_flat,
source.name == server.default_source_name,
# Update output
self._outputs.clear()
for sink in pulse.sink_list():
self._outputs.append(
AudioStream(
sink.name,
sink.description,
sink.volume.value_flat,
sink.name == server.default_sink_name,
)
)
)
except PulseOperationFailed as err:
_LOGGER.error("Error while processing pulse update: %s", err)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.debug("Can't update PulseAudio data: %s", err)
# Update input
self._inputs.clear()
for source in pulse.source_list():
# Filter monitor devices out because we did not use it now
if source.name.endswith(".monitor"):
continue
self._inputs.append(
AudioStream(
source.name,
source.description,
source.volume.value_flat,
source.name == server.default_source_name,
)
)
# Update Sound Card
self._cards.clear()
for card in pulse.card_list():
sound_profiles: List[SoundProfile] = []
# Generate profiles
for profile in card.profile_list:
if not profile.available:
continue
sound_profiles.append(
SoundProfile(
profile.name,
profile.description,
profile.name == card.profile_active.name,
)
)
self._cards.append(
SoundCard(card.name, card.driver, sound_profiles)
)
except PulseOperationFailed as err:
_LOGGER.error("Error while processing pulse update: %s", err)
raise PulseAudioError() from None
except PulseError as err:
_LOGGER.debug("Can't update PulseAudio data: %s", err)
# Run update from pulse server
await self.sys_run_in_executor(_update)

57
supervisor/hwmon.py Normal file
View File

@ -0,0 +1,57 @@
"""Supervisor Hardware monitor based on udev."""
from datetime import timedelta
import logging
from pprint import pformat
from typing import Optional
import pyudev
from .coresys import CoreSysAttributes, CoreSys
from .utils import AsyncCallFilter
_LOGGER: logging.Logger = logging.getLogger(__name__)
class HwMonitor(CoreSysAttributes):
"""Hardware monitor for supervisor."""
def __init__(self, coresys: CoreSys):
"""Initialize Hardware Monitor object."""
self.coresys: CoreSys = coresys
self.context = pyudev.Context()
self.monitor = pyudev.Monitor.from_netlink(self.context)
self.observer: Optional[pyudev.MonitorObserver] = None
async def load(self) -> None:
"""Start hardware monitor."""
self.observer = pyudev.MonitorObserver(self.monitor, self._udev_events)
self.observer.start()
_LOGGER.info("Start Supervisor hardware monitor")
async def unload(self) -> None:
"""Shutdown sessions."""
if self.observer is None:
return
self.observer.stop()
_LOGGER.info("Stop Supervisor hardware monitor")
def _udev_events(self, action: str, device: pyudev.Device):
"""Incomming events from udev.
This is inside a observe thread and need pass into our eventloop.
"""
_LOGGER.debug("Hardware monitor: %s - %s", action, pformat(device))
self.sys_loop.call_soon_threadsafe(self._async_udev_events, action, device)
def _async_udev_events(self, action: str, device: pyudev.Device):
"""Incomming events from udev into loop."""
# Sound changes
if device.subsystem == "sound":
self._action_sound(device)
@AsyncCallFilter(timedelta(seconds=5))
def _action_sound(self, device: pyudev.Device):
"""Process sound actions."""
_LOGGER.info("Detect changed audio hardware")
self.sys_loop.call_later(5, self.sys_create_task, self.sys_host.sound.update())

View File

@ -12,7 +12,6 @@ import pyudev
from ..const import ATTR_DEVICES, ATTR_NAME, ATTR_TYPE, CHAN_ID, CHAN_TYPE
from ..exceptions import HardwareNotSupportedError
_LOGGER: logging.Logger = logging.getLogger(__name__)
ASOUND_CARDS: Path = Path("/proc/asound/cards")
@ -133,7 +132,6 @@ class Hardware:
def audio_devices(self) -> Dict[str, Any]:
"""Return all available audio interfaces."""
if not ASOUND_CARDS.exists():
_LOGGER.info("No audio devices found")
return {}
try:

View File

@ -36,7 +36,7 @@ def process_lock(method):
class AsyncThrottle:
"""
Decorator that prevents a function from being called more than once every
time period.
time period with blocking.
"""
def __init__(self, delta):
@ -64,6 +64,32 @@ class AsyncThrottle:
return wrapper
class AsyncCallFilter:
"""
Decorator that prevents a function from being called more than once every
time period.
"""
def __init__(self, delta):
"""Initialize async throttle."""
self.throttle_period = delta
self.time_of_last_call = datetime.min
def __call__(self, method):
"""Throttle function"""
async def wrapper(*args, **kwargs):
"""Throttle function wrapper"""
now = datetime.now()
time_since_last_call = now - self.time_of_last_call
if time_since_last_call > self.throttle_period:
self.time_of_last_call = now
return await method(*args, **kwargs)
return wrapper
def check_port(address: IPv4Address, port: int) -> bool:
"""Check if port is mapped."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)