Skip to content

Commit

Permalink
Add Bluetooth + scene user presence (#1077)
Browse files Browse the repository at this point in the history
* BLE presence scanner

* Update translations + add presence sensor on device state change in scene

* Add new checkuserpresence action in scene

* Add user check presence action server side with tests

* Add en translations

* Fix checkUserpresence front build

Co-authored-by: atrovato <1839717+atrovato@users.noreply.github.com>
  • Loading branch information
Pierre-Gilles and atrovato authored Feb 19, 2021
1 parent 24b6b69 commit 15b6027
Show file tree
Hide file tree
Showing 53 changed files with 1,734 additions and 893 deletions.
2 changes: 2 additions & 0 deletions front/src/components/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import BluetoothDevicePage from '../routes/integration/all/bluetooth/device-page
import BluetoothEditDevicePage from '../routes/integration/all/bluetooth/edit-page';
import BluetoothSetupPage from '../routes/integration/all/bluetooth/setup-page';
import BluetoothSetupPeripheralPage from '../routes/integration/all/bluetooth/setup-page/setup-peripheral';
import BluetoothSettingsPage from '../routes/integration/all/bluetooth/settings-page';

// EweLink
import EweLinkPage from '../routes/integration/all/ewelink/device-page';
Expand Down Expand Up @@ -205,6 +206,7 @@ const AppRouter = connect(
<BluetoothEditDevicePage path="/dashboard/integration/device/bluetooth/:deviceSelector" />
<BluetoothSetupPage path="/dashboard/integration/device/bluetooth/setup" />
<BluetoothSetupPeripheralPage path="/dashboard/integration/device/bluetooth/setup/:uuid" />
<BluetoothSettingsPage path="/dashboard/integration/device/bluetooth/config" />

<ChatPage path="/dashboard/chat" />
<MapPage path="/dashboard/maps" />
Expand Down
8 changes: 7 additions & 1 deletion front/src/config/demo.json
Original file line number Diff line number Diff line change
Expand Up @@ -1668,6 +1668,12 @@
"name": "bluetooth",
"enabled": true
},
"get /api/v1/service/bluetooth/config": {
"presenceScanner": {
"status": "enabled",
"frequency": 60000
}
},
"get /api/v1/service/bluetooth/device": [
{
"id": "fbedb47f-4d25-4381-8923-2633b23192a0",
Expand Down Expand Up @@ -1723,7 +1729,7 @@
}
},
"get /api/v1/service/bluetooth/status": {
"running": true
"ready": true
},
"get /api/v1/service/bluetooth/peripheral": [
{
Expand Down
34 changes: 29 additions & 5 deletions front/src/config/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,8 @@
"title": "Bluetooth",
"description": "Control your Bluetooth devices.",
"deviceTab": "Devices",
"setupTab": "Setup",
"discoverTab": "Discover",
"setupTab": "Presence scanner",
"bluetoothNotReadyError": "Bluetooth device is not reachable, please check it is enabled.",
"device": {
"title": "Bluetooth Devices",
Expand All @@ -657,17 +658,17 @@
"externalIdLabel": "External ID",
"manufacturerLabel": "Manufacturer",
"modelLabel": "Model",
"presenceSensorLabel": "Use this device as presence sensor",
"featuresLabel": "Features",
"noFeatureDiscovered": "No features discovered.",
"featureNamePlaceholder": "Enter feature name",
"saveButton": "Save",
"deleteButton": "Delete",
"editButton": "Edit"
},
"setup": {
"title": "Bluetooth Setup",
"discover": {
"title": "Bluetooth Discover",
"scanButton": "Scan",
"reloadButton": "Reload",
"noDeviceFound": "No bluetooth device discovered.",
"createDeviceInGladys": "Connect in Gladys",
"updateDeviceInGladys": "Update in Gladys",
Expand All @@ -684,6 +685,18 @@
"cancelLabel": "Cancel",
"successLabel": "Done"
}
},
"setup": {
"title": "Presence scanner",
"noConfigLabel": "Configuration not loaded, please retry.",
"errorLabel": "An error occurred while loading configuration.",
"presenceScannerDescription": "The presence scanner feature is doing a bluetooth scan at a defined interval. When a device is detected, the device will be flagged as \"seen\" in Gladys. In scene, you can create a trigger on this state change to set you home when a device is detected at home for example.",
"presenceScannerStatusLabel": "Enable or disable presence scanner",
"presenceScannerFrequencyLabel": "Scanner interval",
"presenceScannerFrequencyUnit": "minutes",
"presenceScannerFrequencyError": "Only integers are accepted.",
"presenceScannerButton": "Scan device presence now",
"saveLabel": "Save configuration"
}
},
"eWeLink": {
Expand Down Expand Up @@ -810,6 +823,15 @@
"userSeenDescription": "This action set the user as \"at home\".",
"userLeftHomeDescription": "This action set the user as \"left home\"."
},
"checkUserPresence": {
"description": "When this action is executed, Gladys checks if one of the selected device was seen at home in the last X minutes. If one of the device was seen, the action does nothing more. If no device was seen, the user is marked as \"left home\".",
"userLabel": "User",
"houseLabel": "House",
"deviceLabel": "Devices",
"minutesLabel": "Devices seen less than",
"minutesPlaceholder": "Duration (in minutes)",
"minutes": "minutes"
},
"httpRequest": {
"description": "This action let you make HTTP requests.",
"methodLabel": "Method",
Expand Down Expand Up @@ -855,7 +877,8 @@
},
"user": {
"set-seen-at-home": "User seen at home",
"set-out-of-home": "User left home"
"set-out-of-home": "User left home",
"check-presence": "Check user presence"
},
"http": {
"request": "Make a HTTP request"
Expand Down Expand Up @@ -887,6 +910,7 @@
"valuePlaceholder": "Value",
"on": "On",
"off": "Off",
"deviceSeen": "If the device is detected",
"onlyExecuteAtThreshold": "Execute only when threshold is passed (and not at every value sent by the device)"
},
"scheduledTrigger": {
Expand Down
34 changes: 29 additions & 5 deletions front/src/config/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,8 @@
"title": "Bluetooth",
"description": "Contrôler vos appareils Bluetooth.",
"deviceTab": "Appareils",
"setupTab": "Configuration",
"discoverTab": "Découverte",
"setupTab": "Scanner de présence",
"bluetoothNotReadyError": "Le module Bluetooth n'est pas disponible, merci de vérifier qu'il est bien activé.",
"device": {
"title": "Appareils Bluetooth",
Expand All @@ -321,17 +322,17 @@
"externalIdLabel": "ID externe",
"manufacturerLabel": "Fabricant",
"modelLabel": "Modèle",
"presenceSensorLabel": "Utiliser cet appareil comme un détecteur de présence",
"featuresLabel": "Fonctionnalités",
"noFeatureDiscovered": "Aucune fonctionnalité détectée.",
"featureNamePlaceholder": "Nom de la fonctionnalité",
"saveButton": "Sauvegarder",
"deleteButton": "Supprimer",
"editButton": "Editer"
},
"setup": {
"title": "Configuration Bluetooth",
"discover": {
"title": "Découverte Bluetooth",
"scanButton": "Rechercher",
"reloadButton": "Recharger",
"noDeviceFound": "Aucun périphérique Bluetooth détecté.",
"createDeviceInGladys": "Connecter dans Gladys",
"updateDeviceInGladys": "Mettre à jour dans Gladys",
Expand All @@ -348,6 +349,18 @@
"cancelLabel": "Annuler",
"successLabel": "Effectué"
}
},
"setup": {
"title": "Scanner de présence",
"noConfigLabel": "La configuration n'est pas chargée, merci de réessayer.",
"errorLabel": "Une erreur est survenue lors du chargement de la configuration.",
"presenceScannerDescription": "Le scanner de présence effectue un scan Bluetooth à interval régulier. Dès qu’un appareil est détecté, l'appareil est marqué comme \"présent\" dans Gladys. Dans les scènes, vous avez la possibilité de créer un déclencheur qui écoute sur cet état. Ainsi, vous pouvez facilement faire une scène qui vous met comme présent à la maison dès qu'un appareil bluetooth est détecté.",
"presenceScannerStatusLabel": "Activer ou désactiver l'analyse de présence d'appareils",
"presenceScannerFrequencyLabel": "Fréquence de scan bluetooth",
"presenceScannerFrequencyUnit": "minutes",
"presenceScannerFrequencyError": "Seul un nombre entier est autorisé.",
"presenceScannerButton": "Scanner la présence maintenant",
"saveLabel": "Enregistrer"
}
},
"telegram": {
Expand Down Expand Up @@ -810,6 +823,15 @@
"userSeenDescription": "Cette action marque l'utilisateur comme présent à la maison.",
"userLeftHomeDescription": "Cette action indique que l'utilisateur a quitté la maison."
},
"checkUserPresence": {
"description": "Lorsque cette action est exécutée, Gladys va regarder si les appareils sélectionnées ont été détectés dans les dernières minutes ( selon le nombre de minutes sélectionnée ci-dessous ). Si un appareil a été détecté, Gladys ne fera rien. Si aucun n'appareil n'a été détecté, Gladys marquera l'utilisateur comme hors de la maison.",
"userLabel": "Utilisateur",
"houseLabel": "Maison",
"deviceLabel": "Appareils",
"minutesLabel": "Appareil vu il y a moins de",
"minutesPlaceholder": "Durée (en minutes)",
"minutes": "minutes"
},
"httpRequest": {
"description": "Cette action permet d'envoyer une requête HTTP.",
"methodLabel": "Méthode",
Expand Down Expand Up @@ -855,7 +877,8 @@
},
"user": {
"set-seen-at-home": "Utilisateur vu à la maison",
"set-out-of-home": "Utilisateur parti de la maison"
"set-out-of-home": "Utilisateur parti de la maison",
"check-presence": "Vérifier la présence"
},
"http": {
"request": "Faire une requête HTTP"
Expand Down Expand Up @@ -887,6 +910,7 @@
"valuePlaceholder": "Valeur",
"on": "On",
"off": "Off",
"deviceSeen": "Si l'appareil est détecté",
"onlyExecuteAtThreshold": "Exécuter seulement lorsque le seuil est passé ( et non pas à chaque valeur envoyée )"
},
"scheduledTrigger": {
Expand Down
15 changes: 13 additions & 2 deletions front/src/routes/integration/all/bluetooth/BluetoothPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const BluetoothPage = ({ children, ...props }) => (
class="list-group-item list-group-item-action d-flex align-items-center"
>
<span class="icon mr-3">
<i class="fe fe-radio" />
<i class="fe fe-link" />
</span>
<Text id="integration.bluetooth.deviceTab" />
</Link>
Expand All @@ -30,7 +30,18 @@ const BluetoothPage = ({ children, ...props }) => (
class="list-group-item list-group-item-action d-flex align-items-center"
>
<span class="icon mr-3">
<i class="fe fe-sliders" />
<i class="fe fe-radio" />
</span>
<Text id="integration.bluetooth.discoverTab" />
</Link>

<Link
href="/dashboard/integration/device/bluetooth/config"
activeClassName="active"
class="list-group-item list-group-item-action d-flex align-items-center"
>
<span class="icon mr-3">
<i class="fe fe-settings" />
</span>
<Text id="integration.bluetooth.setupTab" />
</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Component } from 'preact';
import { Text } from 'preact-i18n';
import cx from 'classnames';

import { PRESENCE_STATUS } from '../../../../../../../server/services/bluetooth/lib/utils/bluetooth.constants';

class BluetoothPresenceScanner extends Component {
toggleStatus = () => {
const newStatus =
this.props.config.status === PRESENCE_STATUS.ENABLED ? PRESENCE_STATUS.DISABLED : PRESENCE_STATUS.ENABLED;
this.props.updateConfig('presenceScanner', 'status', newStatus);
};

updateFrequency = e => {
const { value } = e.target;
const rawFrequency = parseFloat(value, 10);

if (Number.isInteger(rawFrequency) && rawFrequency >= 1) {
this.props.updateConfig('presenceScanner', 'frequency', rawFrequency * 60000);
this.setState({ frequencyError: undefined });
} else {
this.setState({ frequencyError: value });
}
};

render({ config = {}, disabled }, { frequencyError }) {
const enabled = config.status === PRESENCE_STATUS.ENABLED;
const frequency = config.frequency / 60000;

return (
<div>
<div class="alert alert-secondary mb-5">
<Text id="integration.bluetooth.setup.presenceScannerDescription" />
</div>

<div class="form-group">
<label class="custom-switch">
<input
type="radio"
class="custom-switch-input"
checked={enabled}
onClick={this.toggleStatus}
disabled={disabled}
/>
<span class="custom-switch-indicator" />
<span class="custom-switch-description">
<Text id="integration.bluetooth.setup.presenceScannerStatusLabel" />
</span>
</label>
</div>

<div class="form-group">
<label class="form-label">
<Text id="integration.bluetooth.setup.presenceScannerFrequencyLabel" />
</label>
<div class="input-group col-sm-3">
<input
type="number"
disabled={!enabled || disabled}
value={frequencyError || frequency}
min="1"
class={cx('form-control', { 'is-invalid': frequencyError })}
onInput={this.updateFrequency}
/>
<div class="input-group-append">
<span class="input-group-text">
<Text id="integration.bluetooth.setup.presenceScannerFrequencyUnit" />
</span>
</div>
</div>
{frequencyError && (
<div class="invalid-feedback d-block">
<Text id="integration.bluetooth.setup.presenceScannerFrequencyError" />
</div>
)}
</div>
</div>
);
}
}

export default BluetoothPresenceScanner;
Loading

0 comments on commit 15b6027

Please sign in to comment.