Merge branch 'apply' into 'dev'

Apply

See merge request sa4-2020/the-sanmarinoes/frontend!97
This commit is contained in:
Andrea Brites Marto 2020-04-27 15:55:06 +02:00
commit 23ad5e2ff3
12 changed files with 191 additions and 137 deletions

View file

@ -3,19 +3,33 @@ import { connect } from "react-redux";
import { RemoteService } from "../../remote"; import { RemoteService } from "../../remote";
import Device from "./devices/Device"; import Device from "./devices/Device";
import NewSceneDevice from "./NewSceneDevice"; import NewSceneDevice from "./NewSceneDevice";
import { Grid } from "semantic-ui-react"; import { Grid, Button } from "semantic-ui-react";
class ScenesPanel extends Component { class ScenesPanel extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.applyScene = this.applyScene.bind(this);
}
applyScene() {
console.log(this.props.activeScene);
this.props.sceneApply(this.props.activeScene).then(() => {
alert("Scene applied.");
});
} }
render() { render() {
return ( return (
<Grid doubling columns={2} divided="vertically"> <Grid doubling columns={2} divided="vertically">
{!this.props.isActiveDefaultScene ? (
<Grid.Row centered>
<Button color="blue" onClick={this.applyScene}>
Apply Scene
</Button>
</Grid.Row>
) : null}
{!this.props.isActiveDefaultScene {!this.props.isActiveDefaultScene
? this.props.sceneStates.map((e, i) => { ? this.props.sceneStates.map((e, i) => {
console.log(this.props.sceneStates);
return ( return (
<Grid.Column key={i}> <Grid.Column key={i}>
<Device tab={this.props.tab} id={e.id} /> <Device tab={this.props.tab} id={e.id} />
@ -41,8 +55,8 @@ const mapStateToProps = (state, _) => ({
const stateArray = [ const stateArray = [
...state.scenes[state.active.activeScene].sceneStates, ...state.scenes[state.active.activeScene].sceneStates,
].sort(); ].sort();
console.log(stateArray); console.log("STATESCENE", stateArray);
return stateArray; return stateArray.map((id) => state.sceneStates[id]);
} else { } else {
return []; return [];
} }

View file

@ -31,8 +31,8 @@ class Curtain extends Component {
.catch((err) => console.error("curtains update error", err)); .catch((err) => console.error("curtains update error", err));
} else { } else {
this.props.updateState( this.props.updateState(
{ id: this.props.sceneState.id, on: on }, { id: this.props.stateOrDevice.id, on: on },
this.props.sceneState.kind this.props.stateOrDevice.kind
); );
} }
}; };
@ -64,8 +64,8 @@ class Curtain extends Component {
.catch((err) => console.error("curtain update error", err)); .catch((err) => console.error("curtain update error", err));
} else { } else {
this.props.updateState( this.props.updateState(
{ id: this.props.sceneState.id, intensity: intensity }, { id: this.props.stateOrDevice.id, intensity: intensity },
this.props.sceneState.kind this.props.stateOrDevice.kind
); );
} }
}; };

View file

@ -19,7 +19,7 @@ class Device extends React.Component {
this.modalRef = React.createRef(); this.modalRef = React.createRef();
this.edit = this.edit.bind(this); this.edit = this.edit.bind(this);
this.resetSmartPlug = this.resetSmartPlug.bind(this); this.resetSmartPlug = this.resetSmartPlug.bind(this);
this.deleteState=this.deleteState.bind(this); this.deleteState = this.deleteState.bind(this);
} }
edit() { edit() {
@ -77,60 +77,28 @@ class Device extends React.Component {
/> />
); );
case "motionSensor": case "motionSensor":
return ( return <Sensor tab={this.props.tab} id={this.props.stateOrDevice.id} />;
<Sensor
tab={this.props.tab}
sceneState={this.props.sceneState}
id={this.props.stateOrDevice.id}
/>
);
case "buttonDimmer": case "buttonDimmer":
return ( return (
<ButtonDimmer <ButtonDimmer tab={this.props.tab} id={this.props.stateOrDevice.id} />
tab={this.props.tab}
sceneState={this.props.sceneState}
id={this.props.stateOrDevice.id}
/>
); );
case "knobDimmer": case "knobDimmer":
return ( return (
<KnobDimmer <KnobDimmer tab={this.props.tab} id={this.props.stateOrDevice.id} />
tab={this.props.tab}
sceneState={this.props.sceneState}
id={this.props.stateOrDevice.id}
/>
); );
case "smartPlug": case "smartPlug":
return ( return (
<SmartPlug <SmartPlug tab={this.props.tab} id={this.props.stateOrDevice.id} />
tab={this.props.tab}
sceneState={this.props.sceneState}
id={this.props.stateOrDevice.id}
/>
); );
case "switch": case "switch":
return ( return (
<Switcher <Switcher tab={this.props.tab} id={this.props.stateOrDevice.id} />
tab={this.props.tab}
sceneState={this.props.sceneState}
id={this.props.stateOrDevice.id}
/>
); );
case "dimmableLight": case "dimmableLight":
return ( return <Light id={this.props.stateOrDevice.id} tab={this.props.tab} />;
<Light
id={this.props.stateOrDevice.id}
sceneState={this.props.sceneState}
tab={this.props.tab}
/>
);
case "securityCamera": case "securityCamera":
return ( return (
<Videocam <Videocam id={this.props.stateOrDevice.id} tab={this.props.tab} />
id={this.props.stateOrDevice.id}
sceneState={this.props.sceneState}
tab={this.props.tab}
/>
); );
default: default:
//throw new Error("Device type unknown"); //throw new Error("Device type unknown");
@ -171,11 +139,12 @@ class Device extends React.Component {
</Grid.Column> </Grid.Column>
) : ( ) : (
<Grid.Column textAlign="center"> <Grid.Column textAlign="center">
<Header as="h3"> <Header as="h3">{this.props.device.name}</Header>
{this.props.stateOrDevice {this.props.tab === "Scenes" ? (
? this.props.stateOrDevice.name <Header as="h4">{this.props.roomName}</Header>
: ""} ) : (
</Header> ""
)}
<Button <Button
color="red" color="red"
icon icon
@ -188,6 +157,14 @@ class Device extends React.Component {
</Grid.Column> </Grid.Column>
)} )}
</Grid> </Grid>
{this.props.stateOrDevice ? (
<DeviceSettingsModal
ref={this.modalRef}
id={this.props.stateOrDevice.id}
/>
) : (
""
)}
</Segment> </Segment>
); );
} else { } else {
@ -211,31 +188,31 @@ const mapStateToProps = (state, ownProps) => ({
if (state.active.activeTab === "Devices") { if (state.active.activeTab === "Devices") {
return state.devices[ownProps.id]; return state.devices[ownProps.id];
} else { } else {
console.log(
state.sceneStates,
ownProps.id,
state.sceneStates[ownProps.id]
);
return state.sceneStates[ownProps.id]; return state.sceneStates[ownProps.id];
} }
}, },
get sceneState() { get device() {
if (state.active.activeTab === "Scenes") { if (state.active.activeTab === "Devices") {
const array = [ return state.devices[ownProps.id];
...state.scenes[state.active.activeScene].sceneStates,
].sort();
const deviceState = array.filter((e) => e.id === ownProps.id)[0];
return deviceState;
} else { } else {
return null; return state.devices[state.sceneStates[ownProps.id].deviceId];
}
},
get roomName() {
if (state.active.activeTab === "Scenes") {
const device = state.devices[state.sceneStates[ownProps.id].deviceId];
return state.rooms[device.roomId].name;
} else {
return "";
} }
}, },
get type() { get type() {
console.log("ALPACA", state, ownProps);
if (state.active.activeTab === "Scenes") { if (state.active.activeTab === "Scenes") {
if (state.sceneStates[ownProps.id]) { if (state.sceneStates[ownProps.id]) {
console.log(state.sceneStates[ownProps.id], ownProps.id); //console.log(state.sceneStates[ownProps.id], ownProps.id);
const id = state.sceneStates[ownProps.id].deviceId; const id = state.sceneStates[ownProps.id].deviceId;
console.log(id, state.devices[id].kind); //console.log(id, state.devices[id].kind);
return state.devices[id].kind; return state.devices[id].kind;
} else { } else {
return ""; return "";

View file

@ -66,6 +66,7 @@ class DeviceSettingsModal extends Component {
super(props); super(props);
this.state = { this.state = {
open: false, open: false,
name: this.props.device.name,
}; };
this.updateDevice = this.updateDevice.bind(this); this.updateDevice = this.updateDevice.bind(this);
@ -111,8 +112,7 @@ class DeviceSettingsModal extends Component {
<Modal.Header>Settings of {this.props.device.name}</Modal.Header> <Modal.Header>Settings of {this.props.device.name}</Modal.Header>
<Modal.Content> <Modal.Content>
<SettingsForm <SettingsForm
name={this.props.device.name} name={this.state.name}
type={this.props.device.type}
removeDevice={this.deleteDevice} removeDevice={this.deleteDevice}
saveFunction={this.updateDevice} saveFunction={this.updateDevice}
/> />

View file

@ -72,10 +72,18 @@ class Light extends Component {
.saveDevice({ ...this.props.stateOrDevice, on }) .saveDevice({ ...this.props.stateOrDevice, on })
.catch((err) => console.error("regular light update error", err)); .catch((err) => console.error("regular light update error", err));
} else { } else {
this.props.updateState( this.props
{ id: this.props.sceneState.id, on: on }, .updateState(
this.props.sceneState.kind {
); id: this.props.stateOrDevice.id,
on: on,
sceneId: this.props.stateOrDevice.sceneId,
},
this.props.stateOrDevice.kind
)
.then((res) => {
console.log(res);
});
} }
}; };
@ -110,8 +118,8 @@ class Light extends Component {
.catch((err) => console.error("regular light update error", err)); .catch((err) => console.error("regular light update error", err));
} else { } else {
this.props.updateState( this.props.updateState(
{ id: this.props.sceneState.id, intensity: intensity }, { id: this.props.stateOrDevice.id, intensity: intensity },
this.props.sceneState.kind this.props.stateOrDevice.kind
); );
} }
}; };

View file

@ -73,13 +73,16 @@ class Sensor extends Component {
this.props.stateOrDevice.kind === "motionSensor" && this.props.stateOrDevice.kind === "motionSensor" &&
this.props.stateOrDevice.detected !== prevProps.stateOrDevice.detected this.props.stateOrDevice.detected !== prevProps.stateOrDevice.detected
) { ) {
this.setState({ motion: true, detected: this.props.stateOrDevice.detected }); this.setState({
motion: true,
detected: this.props.stateOrDevice.detected,
});
} }
} }
componentDidMount() { componentDidMount() {
if (this.props.stateOrDevice.kind === "sensor") { if (this.props.stateOrDevice.kind === "sensor") {
switch (this.props.device.sensor) { switch (this.props.stateOrDevice.sensor) {
case "TEMPERATURE": case "TEMPERATURE":
this.units = "ºC"; this.units = "ºC";
this.colors = temperatureSensorColors; this.colors = temperatureSensorColors;
@ -192,10 +195,10 @@ class Sensor extends Component {
} }
const mapStateToProps = (state, ownProps) => ({ const mapStateToProps = (state, ownProps) => ({
get stateOrDevice(){ get stateOrDevice() {
if(state.active.activeTab==="Devices"){ if (state.active.activeTab === "Devices") {
return state.devices[ownProps.id]; return state.devices[ownProps.id];
}else{ } else {
return state.sceneStates[ownProps.id]; return state.sceneStates[ownProps.id];
} }
}, },

View file

@ -33,12 +33,15 @@ class SmartPlug extends Component {
onClickDevice = () => { onClickDevice = () => {
const on = !this.turnedOn; const on = !this.turnedOn;
if(this.props.tab==="Devices"){ if (this.props.tab === "Devices") {
this.props this.props
.saveDevice({ ...this.props.stateOrDevice, on }) .saveDevice({ ...this.props.stateOrDevice, on })
.catch((err) => console.error("smart plug update error", err)); .catch((err) => console.error("smart plug update error", err));
}else{ } else {
this.props.updateState({ id: this.props.sceneState.id, on: on},this.props.sceneState.kind); this.props.updateState(
{ id: this.props.stateOrDevice.id, on: on },
this.props.stateOrDevice.kind
);
} }
}; };
@ -68,10 +71,10 @@ class SmartPlug extends Component {
} }
const mapStateToProps = (state, ownProps) => ({ const mapStateToProps = (state, ownProps) => ({
get stateOrDevice(){ get stateOrDevice() {
if(state.active.activeTab==="Devices"){ if (state.active.activeTab === "Devices") {
return state.devices[ownProps.id]; return state.devices[ownProps.id];
}else{ } else {
return state.sceneStates[ownProps.id]; return state.sceneStates[ownProps.id];
} }
}, },

View file

@ -20,7 +20,8 @@ class Thermostats extends Component {
super(props); super(props);
this.state = { this.state = {
targetTemperature: this.props.stateOrDevice.targetTemperature, targetTemperature: this.props.stateOrDevice.targetTemperature,
internalSensorTemperature: this.props.stateOrDevice.internalSensorTemperature, internalSensorTemperature: this.props.stateOrDevice
.internalSensorTemperature,
mode: this.props.stateOrDevice.mode, mode: this.props.stateOrDevice.mode,
measuredTemperature: this.props.stateOrDevice.measuredTemperature, measuredTemperature: this.props.stateOrDevice.measuredTemperature,
useExternalSensors: this.props.stateOrDevice.useExternalSensors, useExternalSensors: this.props.stateOrDevice.useExternalSensors,
@ -63,34 +64,46 @@ class Thermostats extends Component {
//i came to the conclusion that is not possible to set mode. //i came to the conclusion that is not possible to set mode.
//this.mode = "HEATING"; //this.mode = "HEATING";
const turnOn = mode; const turnOn = mode;
if(this.props.tab==="Devices"){ if (this.props.tab === "Devices") {
this.props this.props
.saveDevice({ ...this.props.stateOrDevice, turnOn }) .saveDevice({ ...this.props.stateOrDevice, turnOn })
.catch((err) => console.error("thermostat update error", err)); .catch((err) => console.error("thermostat update error", err));
}else{ } else {
this.props.updateState({ id: this.props.sceneState.id, turnOn: turnOn },this.props.sceneState.kind); this.props.updateState(
{ id: this.props.stateOrDevice.id, turnOn: turnOn },
this.props.stateOrDevice.kind
);
} }
} }
onClickDevice = () => { onClickDevice = () => {
const on = !this.turnedOn; const on = !this.turnedOn;
if(this.props.tab==="Devices"){ if (this.props.tab === "Devices") {
this.props this.props
.saveDevice({ ...this.props.stateOrDevice, on }) .saveDevice({ ...this.props.stateOrDevice, on })
.catch((err) => console.error("thermostat update error", err)); .catch((err) => console.error("thermostat update error", err));
}else{ } else {
this.props.updateState({ id: this.props.sceneState.id, on: on },this.props.sceneState.kind); this.props.updateState(
{ id: this.props.stateOrDevice.id, on: on },
this.props.stateOrDevice.kind
);
} }
}; };
//It seems to work //It seems to work
saveTargetTemperature(targetTemperature) { saveTargetTemperature(targetTemperature) {
if(this.props.tab==="Devices"){ if (this.props.tab === "Devices") {
this.props this.props
.saveDevice({ ...this.props.stateOrDevice, targetTemperature }) .saveDevice({ ...this.props.stateOrDevice, targetTemperature })
.catch((err) => console.error("thermostat update error", err)); .catch((err) => console.error("thermostat update error", err));
}else{ } else {
this.props.updateState({id: this.props.sceneState.id, targetTemperature: targetTemperature},this.props.sceneState.kind); this.props.updateState(
{
id: this.props.stateOrDevice.id,
targetTemperature: targetTemperature,
},
this.props.stateOrDevice.kind
);
} }
} }
@ -113,12 +126,18 @@ class Thermostats extends Component {
//I have no idea why it doesn't want to update the temperature //I have no idea why it doesn't want to update the temperature
saveInternalSensorTemperature(internalSensorTemperature) { saveInternalSensorTemperature(internalSensorTemperature) {
if(this.props.tab==="Devices"){ if (this.props.tab === "Devices") {
this.props this.props
.saveDevice({ ...this.props.device, internalSensorTemperature }) .saveDevice({ ...this.props.device, internalSensorTemperature })
.catch((err) => console.error("thermostat update error", err)); .catch((err) => console.error("thermostat update error", err));
}else{ } else {
this.props.updateState({ id: this.props.sceneState.id, internalSensorTemperature:internalSensorTemperature },this.props.sceneState.kind); this.props.updateState(
{
id: this.props.stateOrDevice.id,
internalSensorTemperature: internalSensorTemperature,
},
this.props.stateOrDevice.kind
);
} }
} }
@ -151,7 +170,6 @@ class Thermostats extends Component {
render() { render() {
return ( return (
<div style={container}> <div style={container}>
<h3 style={deviceName}>{this.props.stateOrDevice.name}</h3> <h3 style={deviceName}>{this.props.stateOrDevice.name}</h3>
<div style={line}></div> <div style={line}></div>
@ -183,12 +201,11 @@ class Thermostats extends Component {
} }
const mapStateToProps = (state, ownProps) => ({ const mapStateToProps = (state, ownProps) => ({
get stateOrDevice(){ get stateOrDevice() {
if(state.active.activeTab==="Devices"){ if (state.active.activeTab === "Devices") {
return state.devices[ownProps.id]; return state.devices[ownProps.id];
}else{ } else {
const sceneState = state.sceneStates[ownProps.id]; return state.sceneStates[ownProps.id];
return state.devices[sceneState];
} }
}, },
}); });

View file

@ -40,7 +40,7 @@ class Videocam extends Component {
</video> </video>
</div> </div>
</StyledDivCamera> </StyledDivCamera>
<Grid columns="equal" divide padded> <Grid columns="equal" padded>
<Grid.Row textAlign="center"> <Grid.Row textAlign="center">
<Grid.Column> <Grid.Column>
<VideocamModal <VideocamModal

View file

@ -396,15 +396,15 @@ export const RemoteService = {
}; };
}, },
deleteState: (id, type)=>{ deleteState: (id, type) => {
return (dispatch)=>{ return (dispatch) => {
let url; let url;
if (type === "dimmableState") { if (type === "dimmableState") {
url = "/dimmableState"; url = "/dimmableState";
} else { } else {
url = "/switchableState"; url = "/switchableState";
} }
return Endpoint.delete(url+`/${id}`) return Endpoint.delete(url + `/${id}`)
.then((_) => dispatch(actions.stateDelete(id))) .then((_) => dispatch(actions.stateDelete(id)))
.catch((err) => { .catch((err) => {
console.warn("state delete error", err); console.warn("state delete error", err);
@ -413,6 +413,19 @@ export const RemoteService = {
}; };
}, },
sceneApply: (id) => {
return (dispatch) => {
let url = `/scene/${id}/apply`;
return Endpoint.post(url)
.then((res) => dispatch(actions.deviceOperationUpdate(res.data)))
.catch((err) => {
console.warn("scene apply error", err);
throw new RemoteError(["Network error"]);
});
};
},
/** /**
* Creates/Updates a device with the given data. If * Creates/Updates a device with the given data. If
* data.id is truthy, then a update call is performed, * data.id is truthy, then a update call is performed,

View file

@ -105,9 +105,8 @@ function reducer(previousState, action) {
createOrUpdateScene(scene); createOrUpdateScene(scene);
} }
break; break;
case "STATE_UPDATE": case "STATES_UPDATE":
//console.log(action.sceneStates); //console.log(action.sceneStates);
newState = previousState;
change = null; change = null;
// if room is given, delete all devices in that room // if room is given, delete all devices in that room
@ -118,7 +117,7 @@ function reducer(previousState, action) {
sceneStates: { $unset: [] }, sceneStates: { $unset: [] },
}; };
const scene = newState.scenes[action.sceneId]; const scene = previousState.scenes[action.sceneId];
for (const stateId of scene.sceneStates) { for (const stateId of scene.sceneStates) {
change.sceneStates.$unset.push(stateId); change.sceneStates.$unset.push(stateId);
} }
@ -147,17 +146,19 @@ function reducer(previousState, action) {
change.scenes[sceneState.sceneId].sceneStates || {}; change.scenes[sceneState.sceneId].sceneStates || {};
const sceneStates = change.scenes[sceneState.sceneId].sceneStates; const sceneStates = change.scenes[sceneState.sceneId].sceneStates;
sceneStates.$add = sceneStates.$add || []; sceneStates.$add = sceneStates.$add || [];
sceneStates.$add.push(sceneState); sceneStates.$add.push(sceneState.id);
} else { } else {
// room does not exist yet, so add to the list of pending // room does not exist yet, so add to the list of pending
// joins // joins
if (!change.pendingJoins.scenes[sceneState.sceneId]) { if (!change.pendingJoins.scenes[sceneState.sceneId]) {
change.pendingJoins.scenes[sceneState.sceneId] = { change.pendingJoins.scenes[sceneState.sceneId] = {
$set: new Set([sceneState]), $set: new Set([sceneState.id]),
}; };
} else { } else {
change.pendingJoins.scenes[sceneState.sceneId].$set.add(sceneState); change.pendingJoins.scenes[sceneState.sceneId].$set.add(
sceneState.id
);
} }
} }
} }
@ -293,10 +294,11 @@ function reducer(previousState, action) {
}; };
if (previousState.scenes[action.sceneState.sceneId]) { if (previousState.scenes[action.sceneState.sceneId]) {
console.log("PREVSTATE", change, previousState);
change.scenes = { change.scenes = {
[action.sceneState.sceneId]: { [action.sceneState.sceneId]: {
sceneStates: { sceneStates: {
$add: [action.sceneState], $add: [action.sceneState.id],
}, },
}, },
}; };
@ -304,12 +306,13 @@ function reducer(previousState, action) {
change.pendingJoins = { change.pendingJoins = {
scenes: { scenes: {
[action.sceneState.sceneId]: { [action.sceneState.sceneId]: {
$add: [action.sceneState], $add: [action.sceneState.id],
}, },
}, },
}; };
} }
newState = update(previousState, change); newState = update(previousState, change);
console.log("NEWSTATE ", newState);
break; break;
case "ROOM_DELETE": case "ROOM_DELETE":
if (!(action.roomId in previousState.rooms)) { if (!(action.roomId in previousState.rooms)) {
@ -368,7 +371,9 @@ function reducer(previousState, action) {
sceneStates: { $unset: [action.stateId] }, sceneStates: { $unset: [action.stateId] },
}; };
if (previousState.scenes[previousState.sceneStates[action.stateId].sceneId]) { if (
previousState.scenes[previousState.sceneStates[action.stateId].sceneId]
) {
change.scenes = { change.scenes = {
[previousState.sceneStates[action.stateId].sceneId]: { [previousState.sceneStates[action.stateId].sceneId]: {
sceneStates: { $remove: [action.stateId] }, sceneStates: { $remove: [action.stateId] },
@ -378,6 +383,16 @@ function reducer(previousState, action) {
newState = update(previousState, change); newState = update(previousState, change);
break; break;
case "SCENE_APPLY":
console.log(action);
//checking that the scene actually exists
/*if (!(action.sceneId in previousState.scenes)) {
console.warn(`Scene ${action.sceneId} does not exist`);
break;
}*/
break;
case "DEVICE_DELETE": case "DEVICE_DELETE":
if (!(action.deviceId in previousState.devices)) { if (!(action.deviceId in previousState.devices)) {
console.warn(`Device to delete ${action.deviceId} does not exist`); console.warn(`Device to delete ${action.deviceId} does not exist`);
@ -450,7 +465,7 @@ function reducer(previousState, action) {
console.warn(`Action type ${action.type} unknown`, action); console.warn(`Action type ${action.type} unknown`, action);
return previousState; return previousState;
} }
console.log("THETRUEALPACA", newState, action.type, action);
return newState; return newState;
} }

View file

@ -30,7 +30,7 @@ const actions = {
sceneState, sceneState,
}), }),
statesUpdate: (sceneId, sceneStates) => ({ statesUpdate: (sceneId, sceneStates) => ({
type: "STATE_UPDATE", type: "STATES_UPDATE",
sceneId, sceneId,
sceneStates, sceneStates,
}), }),
@ -40,10 +40,14 @@ const actions = {
devices, devices,
partial, partial,
}), }),
stateDelete: (stateId)=>({ stateDelete: (stateId) => ({
type: "STATE_DELETE", type: "STATE_DELETE",
stateId, stateId,
}), }),
sceneApply: (sceneId) => ({
type: "SCENE_APPLY",
sceneId,
}),
deviceOperationUpdate: (devices) => ({ deviceOperationUpdate: (devices) => ({
type: "DEVICES_UPDATE", type: "DEVICES_UPDATE",
devices, devices,