Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Google Cast integration #2088

Merged
merged 14 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ WORKDIR /src
ADD . /src
COPY ./static /src/server/static
WORKDIR /src/server
RUN apk add --no-cache --virtual .build-deps make gcc g++ python3 git libffi-dev linux-headers \
RUN apk add --no-cache --virtual .build-deps make gcc g++ python3 py3-setuptools git libffi-dev linux-headers \
&& npm ci --unsafe-perm --production \
&& npm cache clean --force \
&& apk del .build-deps
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile.buildx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ ENV LD_LIBRARY_PATH /lib

WORKDIR /src/server

RUN apk add --no-cache --virtual .build-deps make gcc g++ python3 git libffi-dev linux-headers \
RUN apk add --no-cache --virtual .build-deps make gcc g++ python3 py3-setuptools git libffi-dev linux-headers \
&& npm ci --unsafe-perm --production \
&& npm cache clean --force \
&& apk del .build-deps
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions front/src/components/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ import NetatmoDiscoverPage from '../routes/integration/all/netatmo/discover-page
import SonosDevicePage from '../routes/integration/all/sonos/device-page';
import SonosDiscoveryPage from '../routes/integration/all/sonos/discover-page';

// Google Cast integration
import GoogleCastDevicePage from '../routes/integration/all/google-cast/device-page';
import GoogleCastDiscoveryPage from '../routes/integration/all/google-cast/discover-page';

// ZWaveJS-UI integration
import ZwaveJSUIDevicePage from '../routes/integration/all/zwavejs-ui/device-page';
import ZwaveJSUIDiscoveryPage from '../routes/integration/all/zwavejs-ui/discover-page';
Expand Down Expand Up @@ -296,6 +300,9 @@ const AppRouter = connect(
<SonosDevicePage path="/dashboard/integration/device/sonos" />
<SonosDiscoveryPage path="/dashboard/integration/device/sonos/discover" />

<GoogleCastDevicePage path="/dashboard/integration/device/google-cast" />
<GoogleCastDiscoveryPage path="/dashboard/integration/device/google-cast/discover" />

<ZwaveJSUIDevicePage path="/dashboard/integration/device/zwavejs-ui" />
<ZwaveJSUIDiscoveryPage path="/dashboard/integration/device/zwavejs-ui/discover" />
<ZwaveJSUISetupPage path="/dashboard/integration/device/zwavejs-ui/setup" />
Expand Down
7 changes: 5 additions & 2 deletions front/src/components/boxs/music/EditMusicBox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Select from 'react-select';
import { connect } from 'unistore/preact';

import BaseEditBox from '../baseEditBox';
import { DEVICE_FEATURE_CATEGORIES } from '../../../../../server/utils/constants';
import { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } from '../../../../../server/utils/constants';

class EditMusicBoxComponent extends Component {
updateDevice = option => {
Expand All @@ -18,9 +18,12 @@ class EditMusicBoxComponent extends Component {
await this.setState({
error: false
});
const musicDevices = await this.props.httpClient.get('/api/v1/device', {
let musicDevices = await this.props.httpClient.get('/api/v1/device', {
device_feature_category: DEVICE_FEATURE_CATEGORIES.MUSIC
});
// We only keep music player with at least the "play" capability
// Some music devices like the Nest Mini only exposes the "notification" for now
musicDevices = musicDevices.filter(d => d.features.find(f => f.type === DEVICE_FEATURE_TYPES.MUSIC.PLAY));
const musicDevicesOptions = musicDevices.map(d => ({
label: d.name,
value: d.selector
Expand Down
35 changes: 35 additions & 0 deletions front/src/config/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,41 @@
"conflictError": "Das aktuelle Gerät ist bereits in Gladys vorhanden."
}
},
"google-cast": {
"title": "Google Cast",
"description": "Steuern Sie Google Cast-Geräte in Ihrem lokalen Netzwerk",
"deviceTab": "Geräte",
"discoverTab": "Google Cast Entdeckung",
"setupTab": "Einrichtung",
"documentation": "Google Cast Dokumentation",
"discoverDeviceDescr": "Google Cast-Geräte automatisch scannen",
"nameLabel": "Gerätename",
"namePlaceholder": "Geben Sie den Namen Ihres Geräts ein",
"hostLabel": "IP-Adresse",
"roomLabel": "Zimmer",
"saveButton": "Speichern",
"alreadyCreatedButton": "Bereits erstellt",
"deleteButton": "Löschen",
"device": {
"title": "Google Cast-Geräte in Gladys",
"editButton": "Bearbeiten",
"noDeviceFound": "Kein Google Cast-Gerät gefunden.",
"featuresLabel": "Funktionen"
},
"discover": {
"title": "Geräte in Ihrem lokalen Netzwerk erkannt",
"description": "Google Cast-Geräte werden automatisch erkannt.",
"error": "Fehler beim Entdecken der Google Cast-Geräte. Ist Ihr Google Cast-Lautsprecher eingeschaltet und im lokalen Netzwerk erreichbar?",
"noDeviceFound": "Keine Google Cast-Geräte gefunden.",
"errorWhileScanning": "Beim Scannen ist ein Fehler aufgetreten.",
"scan": "Scannen"
},
"error": {
"defaultError": "Beim Speichern des Geräts ist ein Fehler aufgetreten.",
"defaultDeletionError": "Beim Löschen des Geräts ist ein Fehler aufgetreten.",
"conflictError": "Das aktuelle Gerät ist bereits in Gladys vorhanden."
}
},
"zwavejs-ui": {
"title": "ZWave JS UI",
"description": "Steuern Sie Ihre Geräte in Z-Wave JS UI",
Expand Down
35 changes: 35 additions & 0 deletions front/src/config/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,41 @@
"conflictError": "The current device is already in Gladys."
}
},
"google-cast": {
"title": "Google Cast",
"description": "Control Google Cast devices on your local network",
"deviceTab": "Devices",
"discoverTab": "Google Cast Discovery",
"setupTab": "Setup",
"documentation": "Google Cast Documentation",
"discoverDeviceDescr": "Automatically scan for Google Cast devices",
"nameLabel": "Device Name",
"namePlaceholder": "Enter your device name",
"hostLabel": "IP Address",
"roomLabel": "Room",
"saveButton": "Save",
"alreadyCreatedButton": "Already Created",
"deleteButton": "Delete",
"device": {
"title": "Google Cast Devices in Gladys",
"editButton": "Edit",
"noDeviceFound": "No Google Cast device found.",
"featuresLabel": "Features"
},
"discover": {
"title": "Devices detected on your local network",
"description": "Google Cast devices are automatically discovered.",
"error": "Error discovering Google Cast devices. Is your Google Cast speaker powered on and accessible on the local network?",
"noDeviceFound": "No Google Cast devices have been discovered.",
"errorWhileScanning": "An error occurred while scanning.",
"scan": "Scan"
},
"error": {
"defaultError": "An error occurred while saving the device.",
"defaultDeletionError": "An error occurred while deleting the device.",
"conflictError": "The current device is already in Gladys."
}
},
"zwavejs-ui": {
"title": "ZWave JS UI",
"description": "Control your devices in Z-Wave JS UI",
Expand Down
35 changes: 35 additions & 0 deletions front/src/config/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,41 @@
"conflictError": "L'appareil actuel est déjà dans Gladys."
}
},
"google-cast": {
"title": "Google Cast",
"description": "Contrôler les appareils Google Cast sur votre réseau local",
"deviceTab": "Appareils",
"discoverTab": "Découverte Google Cast",
"setupTab": "Configuration",
"documentation": "Documentation Google Cast",
"discoverDeviceDescr": "Scanner automatiquement les appareils Google Cast",
"nameLabel": "Nom de l'appareil",
"namePlaceholder": "Entrez le nom de votre appareil",
"hostLabel": "Adresse IP",
"roomLabel": "Pièce",
"saveButton": "Sauvegarder",
"alreadyCreatedButton": "Déjà créé",
"deleteButton": "Supprimer",
"device": {
"title": "Appareils Google Cast dans Gladys",
"editButton": "Éditer",
"noDeviceFound": "Aucun appareil Google Cast trouvé.",
"featuresLabel": "Fonctionnalités"
},
"discover": {
"title": "Appareils détectés sur votre réseau local",
"description": "Les appareils Google Cast sont automatiquement découverts.",
"error": "Erreur de découverte des appareils Google Cast. Est-ce que votre enceinte Google Cast est sous tension et accessible sur le réseau local ?",
"noDeviceFound": "Aucun appareil Google Cast n'a été découvert.",
"errorWhileScanning": "Une erreur est survenue lors du scan.",
"scan": "Scanner"
},
"error": {
"defaultError": "Une erreur s'est produite lors de l'enregistrement de l'appareil.",
"defaultDeletionError": "Une erreur s'est produite lors de la suppression de l'appareil.",
"conflictError": "L'appareil actuel est déjà dans Gladys."
}
},
"zwavejs-ui": {
"title": "ZWave JS UI",
"description": "Contrôler vos appareils dans Z-Wave JS UI",
Expand Down
5 changes: 5 additions & 0 deletions front/src/config/integrations/devices.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,10 @@
"key": "zwavejs-ui",
"link": "zwavejs-ui",
"img": "/assets/integrations/cover/zwave-js-ui.jpg"
},
{
"key": "google-cast",
"link": "google-cast",
"img": "/assets/integrations/cover/google-cast.jpg"
}
]
197 changes: 197 additions & 0 deletions front/src/routes/integration/all/google-cast/GoogleCastDeviceBox.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { Component } from 'preact';
import { Text, Localizer, MarkupText } from 'preact-i18n';
import cx from 'classnames';
import get from 'get-value';

import { connect } from 'unistore/preact';

class GoogleCastDeviceBox extends Component {
componentWillMount() {
this.setState({
device: this.props.device
});
}

componentWillReceiveProps(nextProps) {
this.setState({
device: nextProps.device
});
}

updateName = e => {
this.setState({
device: {
...this.state.device,
name: e.target.value
}
});
};

updateRoom = e => {
this.setState({
device: {
...this.state.device,
room_id: e.target.value
}
});
};

saveDevice = async () => {
this.setState({
loading: true,
errorMessage: null
});
try {
let deviceDidNotExist = this.state.device.id === undefined;
const savedDevice = await this.props.httpClient.post(`/api/v1/device`, this.state.device);
if (deviceDidNotExist) {
savedDevice.alreadyExist = true;
}
this.setState({
device: savedDevice
});
} catch (e) {
let errorMessage = 'integration.google-cast.error.defaultError';
if (e.response.status === 409) {
errorMessage = 'integration.google-cast.error.conflictError';
}
this.setState({
errorMessage
});
}
this.setState({
loading: false
});
};

deleteDevice = async () => {
this.setState({
loading: true,
errorMessage: null,
tooMuchStatesError: false,
statesNumber: undefined
});
try {
if (this.state.device.created_at) {
await this.props.httpClient.delete(`/api/v1/device/${this.state.device.selector}`);
}
this.props.getGoogleCastDevices();
} catch (e) {
const status = get(e, 'response.status');
const dataMessage = get(e, 'response.data.message');
if (status === 400 && dataMessage && dataMessage.includes('Too much states')) {
const statesNumber = new Intl.NumberFormat().format(dataMessage.split(' ')[0]);
this.setState({ tooMuchStatesError: true, statesNumber });
} else {
this.setState({
errorMessage: 'integration.google-cast.error.defaultDeletionError'
});
}
}
this.setState({
loading: false
});
};

render(
{ deviceIndex, editable, deleteButton, housesWithRooms },
{ device, loading, errorMessage, tooMuchStatesError, statesNumber }
) {
const validModel = device.features && device.features.length > 0;

return (
<div class="col-md-6">
<div class="card">
<div class="card-header">{device.name}</div>
<div
class={cx('dimmer', {
active: loading
})}
>
<div class="loader" />
<div class="dimmer-content">
<div class="card-body">
{errorMessage && (
<div class="alert alert-danger">
<Text id={errorMessage} />
</div>
)}
{tooMuchStatesError && (
<div class="alert alert-warning">
<MarkupText id="device.tooMuchStatesToDelete" fields={{ count: statesNumber }} />
</div>
)}
<div class="form-group">
<label class="form-label" for={`name_${deviceIndex}`}>
<Text id="integration.google-cast.nameLabel" />
</label>
<Localizer>
<input
id={`name_${deviceIndex}`}
type="text"
value={device.name}
onInput={this.updateName}
class="form-control"
placeholder={<Text id="integration.google-cast.namePlaceholder" />}
disabled={!editable || !validModel}
/>
</Localizer>
</div>

{housesWithRooms && (
<div class="form-group">
<label class="form-label" for={`room_${deviceIndex}`}>
<Text id="integration.google-cast.roomLabel" />
</label>
<select
id={`room_${deviceIndex}`}
onChange={this.updateRoom}
class="form-control"
disabled={!editable || !validModel}
>
<option value="">
<Text id="global.emptySelectOption" />
</option>
{housesWithRooms &&
housesWithRooms.map(house => (
<optgroup label={house.name}>
{house.rooms.map(room => (
<option selected={room.id === device.room_id} value={room.id}>
{room.name}
</option>
))}
</optgroup>
))}
</select>
</div>
)}

<div class="form-group">
{device.alreadyExist && (
<button class="btn btn-primary mr-2" disabled="true">
<Text id="integration.google-cast.alreadyCreatedButton" />
</button>
)}

{!device.alreadyExist && (
<button onClick={this.saveDevice} class="btn btn-success mr-2">
<Text id="integration.google-cast.saveButton" />
</button>
)}

{deleteButton && (
<button onClick={this.deleteDevice} class="btn btn-danger">
<Text id="integration.google-cast.deleteButton" />
</button>
)}
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}

export default connect('httpClient', {})(GoogleCastDeviceBox);
Loading
Loading