Merge branch '54-users-can-create-basic-automations' into 'dev'

Resolve "Users can create basic automations"

Closes #54

See merge request sa4-2020/the-sanmarinoes/backend!80
This commit is contained in:
Claudio Maggioni 2020-04-23 11:20:55 +02:00
commit df8a9e6449
47 changed files with 703 additions and 157 deletions

View File

@ -1,6 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.ButtonDimmerDimRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.ButtonDimmerDimRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
@ -21,7 +20,6 @@ public class ButtonDimmerController
private DeviceService deviceService; private DeviceService deviceService;
private ButtonDimmerRepository buttonDimmerRepository; private ButtonDimmerRepository buttonDimmerRepository;
private DimmableRepository<Dimmable> dimmableRepository;
@Autowired @Autowired
protected ButtonDimmerController( protected ButtonDimmerController(
@ -31,12 +29,14 @@ public class ButtonDimmerController
super(inputRepository, outputRepository, DimmableLight.BUTTON_DIMMER_DIMMABLE_CONNECTOR); super(inputRepository, outputRepository, DimmableLight.BUTTON_DIMMER_DIMMABLE_CONNECTOR);
this.deviceService = deviceService; this.deviceService = deviceService;
this.buttonDimmerRepository = inputRepository; this.buttonDimmerRepository = inputRepository;
this.dimmableRepository = outputRepository;
} }
@PostMapping @PostMapping
public ButtonDimmer create( public ButtonDimmer create(
@Valid @RequestBody final GenericDeviceSaveReguest bd, final Principal principal) { @Valid @RequestBody final GenericDeviceSaveReguest bd, final Principal principal)
throws NotFoundException {
deviceService.throwIfRoomNotOwned(bd.getRoomId(), principal.getName());
ButtonDimmer newBD = new ButtonDimmer(); ButtonDimmer newBD = new ButtonDimmer();
newBD.setName(bd.getName()); newBD.setName(bd.getName());
newBD.setRoomId(bd.getRoomId()); newBD.setRoomId(bd.getRoomId());
@ -62,7 +62,7 @@ public class ButtonDimmerController
break; break;
} }
dimmableRepository.saveAll(buttonDimmer.getOutputs()); deviceService.saveAllAsOwner(buttonDimmer.getOutputs(), principal.getName(), false);
return buttonDimmer.getOutputs(); return buttonDimmer.getOutputs();
} }

View File

@ -1,6 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.DimmableSaveRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.DimmableSaveRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
@ -31,7 +30,9 @@ public class CurtainsController {
@PostMapping @PostMapping
public Curtains create( public Curtains create(
@Valid @RequestBody DimmableSaveRequest curtain, final Principal principal) { @Valid @RequestBody DimmableSaveRequest curtain, final Principal principal)
throws NotFoundException {
deviceService.throwIfRoomNotOwned(curtain.getRoomId(), principal.getName());
return save(new Curtains(), curtain, principal); return save(new Curtains(), curtain, principal);
} }

View File

@ -1,6 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.DimmableSaveRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.DimmableSaveRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
@ -58,6 +57,7 @@ public class DimmableLightController extends GuestEnabledController<DimmableLigh
public DimmableLight create( public DimmableLight create(
@Valid @RequestBody DimmableSaveRequest dl, final Principal principal) @Valid @RequestBody DimmableSaveRequest dl, final Principal principal)
throws NotFoundException { throws NotFoundException {
deviceService.throwIfRoomNotOwned(dl.getRoomId(), principal.getName());
return save(new DimmableLight(), dl, principal.getName(), null); return save(new DimmableLight(), dl, principal.getName(), null);
} }

View File

@ -1,19 +1,20 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import com.google.gson.Gson; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
/** /**
* An abstract controller for an input device that has output connected to it. Aids to create the * An abstract controller for an input device that has output connected to it. Aids to create the
* output add and output remove route * output add and output remove route
@ -26,14 +27,16 @@ public abstract class InputDeviceConnectionController<
private class Connection { private class Connection {
private final I input; private final I input;
private final List<O> output; private final List<O> outputs;
private Connection(I input, List<O> output) { private Connection(I input, List<O> outputs) {
this.input = input; this.input = input;
this.output = output; this.outputs = outputs;
} }
} }
@Autowired private DeviceService deviceService;
private DeviceRepository<I> inputRepository; private DeviceRepository<I> inputRepository;
private DeviceRepository<O> outputReposiory; private DeviceRepository<O> outputReposiory;
@ -56,17 +59,17 @@ public abstract class InputDeviceConnectionController<
this.connector = connector; this.connector = connector;
} }
private Connection checkConnectionIDs(Long inputId, List<Long> outputs) private Connection checkConnectionIDs(Long inputId, List<Long> outputs, String username)
throws NotFoundException { throws NotFoundException {
final I input = final I input =
inputRepository inputRepository
.findById(inputId) .findByIdAndUsername(inputId, username)
.orElseThrow(() -> new NotFoundException("input device")); .orElseThrow(() -> new NotFoundException("input device"));
final List<O> outputDevices = new ArrayList<>(); final List<O> outputDevices = new ArrayList<>();
for (final Long outputId : outputs) { for (final Long outputId : outputs) {
outputDevices.add( outputDevices.add(
outputReposiory outputReposiory
.findById(outputId) .findByIdAndUsername(outputId, username)
.orElseThrow(() -> new NotFoundException("output device"))); .orElseThrow(() -> new NotFoundException("output device")));
} }
return new Connection(input, outputDevices); return new Connection(input, outputDevices);
@ -76,19 +79,19 @@ public abstract class InputDeviceConnectionController<
* Implements the output device connection creation (add) route * Implements the output device connection creation (add) route
* *
* @param inputId input device id * @param inputId input device id
* @param outputId output device id list * @param outputs output device id list
* @return the list of output devices attached to the input device of id inputId * @return the list of output devices attached to the input device of id inputId
* @throws NotFoundException if inputId or outputId are not valid * @throws NotFoundException if inputId or outputId are not valid
*/ */
protected Set<? extends OutputDevice> addOutput(Long inputId, List<Long> outputId) protected Set<? extends OutputDevice> addOutput(
throws NotFoundException { Long inputId, List<Long> outputs, String username) throws NotFoundException {
final Connection pair = checkConnectionIDs(inputId, outputId); final Connection pair = checkConnectionIDs(inputId, outputs, username);
for (final O o : pair.output) { for (final O o : pair.outputs) {
connector.connect(pair.input, o, true); connector.connect(pair.input, o, true);
} }
outputReposiory.saveAll(pair.output); deviceService.saveAllAsOwner(pair.outputs, username, false);
return pair.input.getOutputs(); return pair.input.getOutputs();
} }
@ -96,33 +99,37 @@ public abstract class InputDeviceConnectionController<
* Implements the output device connection destruction (remove) route * Implements the output device connection destruction (remove) route
* *
* @param inputId input device id * @param inputId input device id
* @param outputId output device id list * @param outputs output device id list
* @return the list of output devices attached to the input device of id inputId * @return the list of output devices attached to the input device of id inputId
* @throws NotFoundException if inputId or outputId are not valid * @throws NotFoundException if inputId or outputId are not valid
*/ */
protected Set<? extends OutputDevice> removeOutput(Long inputId, List<Long> outputId) protected Set<? extends OutputDevice> removeOutput(
throws NotFoundException { Long inputId, List<Long> outputs, String username) throws NotFoundException {
final Connection pair = checkConnectionIDs(inputId, outputId); final Connection pair = checkConnectionIDs(inputId, outputs, username);
for (final O o : pair.output) { for (final O o : pair.outputs) {
connector.connect(pair.input, o, false); connector.connect(pair.input, o, false);
} }
outputReposiory.saveAll(pair.output); deviceService.saveAllAsOwner(pair.outputs, username, false);
return pair.input.getOutputs(); return pair.input.getOutputs();
} }
@PostMapping("/{id}/lights") @PostMapping("/{id}/lights")
public List<OutputDevice> addLight( public List<OutputDevice> addLight(
@PathVariable("id") long inputId, @RequestBody List<Long> lightId) @PathVariable("id") long inputId,
@RequestBody List<Long> lightId,
final Principal principal)
throws NotFoundException { throws NotFoundException {
return toList(addOutput(inputId, lightId)); return toList(addOutput(inputId, lightId, principal.getName()));
} }
@DeleteMapping("/{id}/lights") @DeleteMapping("/{id}/lights")
public List<OutputDevice> removeLight( public List<OutputDevice> removeLight(
@PathVariable("id") long inputId, @RequestBody List<Long> lightId) @PathVariable("id") long inputId,
@RequestBody List<Long> lightId,
final Principal principal)
throws NotFoundException { throws NotFoundException {
return toList(removeOutput(inputId, lightId)); return toList(removeOutput(inputId, lightId, principal.getName()));
} }
} }

View File

@ -1,6 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.KnobDimmerDimRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.KnobDimmerDimRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
@ -20,19 +19,19 @@ public class KnobDimmerController extends InputDeviceConnectionController<KnobDi
@Autowired private DeviceService deviceService; @Autowired private DeviceService deviceService;
@Autowired private KnobDimmerRepository knobDimmerRepository; @Autowired private KnobDimmerRepository knobDimmerRepository;
@Autowired private DimmableRepository<Dimmable> dimmableRepository;
@Autowired @Autowired
protected KnobDimmerController( protected KnobDimmerController(
KnobDimmerRepository inputRepository, DimmableRepository<Dimmable> outputRepository) { KnobDimmerRepository inputRepository, DimmableRepository<Dimmable> outputRepository) {
super(inputRepository, outputRepository, Dimmable.KNOB_DIMMER_DIMMABLE_CONNECTOR); super(inputRepository, outputRepository, Dimmable.KNOB_DIMMER_DIMMABLE_CONNECTOR);
this.knobDimmerRepository = inputRepository; this.knobDimmerRepository = inputRepository;
this.dimmableRepository = outputRepository;
} }
@PostMapping @PostMapping
public KnobDimmer create( public KnobDimmer create(
@Valid @RequestBody GenericDeviceSaveReguest kd, final Principal principal) { @Valid @RequestBody GenericDeviceSaveReguest kd, final Principal principal)
throws NotFoundException {
deviceService.throwIfRoomNotOwned(kd.getRoomId(), principal.getName());
KnobDimmer newKD = new KnobDimmer(); KnobDimmer newKD = new KnobDimmer();
newKD.setName(kd.getName()); newKD.setName(kd.getName());
newKD.setRoomId(kd.getRoomId()); newKD.setRoomId(kd.getRoomId());
@ -50,7 +49,7 @@ public class KnobDimmerController extends InputDeviceConnectionController<KnobDi
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
dimmer.setLightIntensity(bd.getIntensity()); dimmer.setLightIntensity(bd.getIntensity());
dimmableRepository.saveAll(dimmer.getOutputs()); deviceService.saveAllAsOwner(dimmer.getOutputs(), principal.getName(), false);
return dimmer.getOutputs(); return dimmer.getOutputs();
} }

View File

@ -1,11 +1,11 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensor; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensor;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensorRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensorRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.MotionSensorService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint; import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
import java.security.Principal; import java.security.Principal;
import javax.validation.Valid; import javax.validation.Valid;
@ -19,14 +19,15 @@ import org.springframework.web.bind.annotation.*;
public class MotionSensorController { public class MotionSensorController {
@Autowired private DeviceService deviceService; @Autowired private DeviceService deviceService;
@Autowired private MotionSensorService motionSensorService;
@Autowired private MotionSensorRepository motionSensorService; @Autowired private MotionSensorRepository motionSensorRepository;
@Autowired private SensorSocketEndpoint sensorSocketEndpoint; @Autowired private SensorSocketEndpoint sensorSocketEndpoint;
@PostMapping @PostMapping
public MotionSensor create( public MotionSensor create(
@Valid @RequestBody GenericDeviceSaveReguest ms, final Principal principal) { @Valid @RequestBody GenericDeviceSaveReguest ms, final Principal principal)
throws NotFoundException {
deviceService.throwIfRoomNotOwned(ms.getRoomId(), principal.getName());
MotionSensor newMS = new MotionSensor(); MotionSensor newMS = new MotionSensor();
newMS.setName(ms.getName()); newMS.setName(ms.getName());
newMS.setRoomId(ms.getRoomId()); newMS.setRoomId(ms.getRoomId());
@ -34,23 +35,6 @@ public class MotionSensorController {
return deviceService.saveAsOwner(newMS, principal.getName()); return deviceService.saveAsOwner(newMS, principal.getName());
} }
/**
* Updates detection status of given motion sensor and propagates update throgh socket
*
* @param sensor the motion sensor to update
* @param detected the new detection status
* @return the updated motion sensor
*/
public MotionSensor updateDetectionFromMotionSensor(MotionSensor sensor, boolean detected) {
sensor.setDetected(detected);
final MotionSensor toReturn = motionSensorService.save(sensor);
sensorSocketEndpoint.queueDeviceUpdate(
sensor, motionSensorService.findUser(sensor.getId()));
return toReturn;
}
@PutMapping("/{id}/detect") @PutMapping("/{id}/detect")
public MotionSensor updateDetection( public MotionSensor updateDetection(
@PathVariable("id") Long sensorId, @PathVariable("id") Long sensorId,
@ -58,11 +42,12 @@ public class MotionSensorController {
final Principal principal) final Principal principal)
throws NotFoundException { throws NotFoundException {
return updateDetectionFromMotionSensor( return motionSensorService.updateDetectionFromMotionSensor(
motionSensorService motionSensorRepository
.findByIdAndUsername(sensorId, principal.getName()) .findByIdAndUsername(sensorId, principal.getName())
.orElseThrow(NotFoundException::new), .orElseThrow(NotFoundException::new),
detected); detected,
principal.getName());
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")

View File

@ -74,6 +74,7 @@ public class RegularLightController extends GuestEnabledController<RegularLight>
public RegularLight create( public RegularLight create(
@Valid @RequestBody SwitchableSaveRequest rl, final Principal principal) @Valid @RequestBody SwitchableSaveRequest rl, final Principal principal)
throws NotFoundException { throws NotFoundException {
deviceService.throwIfRoomNotOwned(rl.getRoomId(), principal.getName());
return save(new RegularLight(), rl, principal.getName(), null); return save(new RegularLight(), rl, principal.getName(), null);
} }

View File

@ -49,7 +49,11 @@ public class RoomController {
final Principal principal) final Principal principal)
throws NotFoundException { throws NotFoundException {
List<Room> rooms = toList(roomRepository.findAll()); List<Room> rooms =
toList(
hostId != null
? roomRepository.findByUserId(hostId)
: roomRepository.findByUsername(principal.getName()));
return fetchOwnerOrGuest(rooms, hostId, principal); return fetchOwnerOrGuest(rooms, hostId, principal);
} }
@ -60,10 +64,6 @@ public class RoomController {
@RequestParam(value = "hostId", required = false) Long hostId) @RequestParam(value = "hostId", required = false) Long hostId)
throws NotFoundException { throws NotFoundException {
Room room = roomRepository.findById(id).orElseThrow(NotFoundException::new); Room room = roomRepository.findById(id).orElseThrow(NotFoundException::new);
/* Very ugly way of avoiding code duplication. If this method call throws no exception,
* we can return the room safely. I pass null as I do not return a list in this case.
* Refer to fetchOwnerOrGuest for further information.
*/
fetchOwnerOrGuest(null, hostId, principal); fetchOwnerOrGuest(null, hostId, principal);
return room; return room;
} }
@ -115,11 +115,16 @@ public class RoomController {
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
public void deleteById(@PathVariable("id") long id) { public void deleteById(@PathVariable("id") long id, final Principal principal)
throws NotFoundException {
switchRepository.deleteAllByRoomId(id); switchRepository.deleteAllByRoomId(id);
knobDimmerRepository.deleteAllByRoomId(id); knobDimmerRepository.deleteAllByRoomId(id);
buttonDimmerRepository.deleteAllByRoomId(id); buttonDimmerRepository.deleteAllByRoomId(id);
roomRepository.deleteById(id); final Room r =
roomRepository
.findByIdAndUsername(id, principal.getName())
.orElseThrow(NotFoundException::new);
roomRepository.delete(r);
} }
/** /**

View File

@ -5,8 +5,8 @@ import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SceneSaveRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SceneSaveRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.SceneService;
import java.security.Principal; import java.security.Principal;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.validation.Valid; import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -26,20 +26,20 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/scene") @RequestMapping("/scene")
public class SceneController { public class SceneController {
@Autowired private SceneRepository sceneService; @Autowired private SceneRepository sceneRepository;
@Autowired private SceneService sceneService;
@Autowired private UserRepository userService; @Autowired private UserRepository userService;
@Autowired private StateRepository<State<?>> stateService; @Autowired private StateRepository<State<?>> stateService;
@Autowired private DeviceRepository<Device> deviceRepository;
@GetMapping @GetMapping
public List<Scene> findAll(Principal principal) { public List<Scene> findAll(Principal principal) {
return toList(sceneService.findByUsername(principal.getName())); return toList(sceneRepository.findByUsername(principal.getName()));
} }
@GetMapping("/{id}") @GetMapping("/{id}")
public @ResponseBody Scene findById(@PathVariable("id") long id, Principal principal) public @ResponseBody Scene findById(@PathVariable("id") long id, Principal principal)
throws NotFoundException { throws NotFoundException {
return sceneService return sceneRepository
.findByIdAndUsername(id, principal.getName()) .findByIdAndUsername(id, principal.getName())
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
} }
@ -55,29 +55,20 @@ public class SceneController {
newScene.setUserId(userId); newScene.setUserId(userId);
newScene.setName(s.getName()); newScene.setName(s.getName());
newScene.setGuestAccessEnabled(s.isGuestAccessEnabled()); newScene.setGuestAccessEnabled(s.isGuestAccessEnabled());
return sceneService.save(newScene); return sceneRepository.save(newScene);
} }
@PostMapping("/{id}/apply") @PostMapping("/{id}/apply")
public @ResponseBody List<Device> apply(@PathVariable("id") long id, final Principal principal) public @ResponseBody List<Device> apply(@PathVariable("id") long id, final Principal principal)
throws NotFoundException { throws NotFoundException {
final Scene newScene = final Scene newScene =
sceneService sceneRepository
.findByIdAndUsername(id, principal.getName()) .findByIdAndUsername(id, principal.getName())
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
final List<Device> updated = new ArrayList<>(); return sceneService.apply(newScene);
for (final State<?> s : newScene.getStates()) {
s.apply();
updated.add(s.getDevice());
}
deviceRepository.saveAll(updated);
return updated;
} }
@PutMapping("/{id}") @PutMapping("/{id}")
@ -85,7 +76,7 @@ public class SceneController {
@PathVariable("id") long id, @RequestBody SceneSaveRequest s, final Principal principal) @PathVariable("id") long id, @RequestBody SceneSaveRequest s, final Principal principal)
throws NotFoundException { throws NotFoundException {
final Scene newScene = final Scene newScene =
sceneService sceneRepository
.findByIdAndUsername(id, principal.getName()) .findByIdAndUsername(id, principal.getName())
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
@ -95,13 +86,13 @@ public class SceneController {
newScene.setGuestAccessEnabled(s.isGuestAccessEnabled()); newScene.setGuestAccessEnabled(s.isGuestAccessEnabled());
return sceneService.save(newScene); return sceneRepository.save(newScene);
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
public void deleteById(@PathVariable("id") long id) { public void deleteById(@PathVariable("id") long id) {
stateService.deleteAllBySceneId(id); stateService.deleteAllBySceneId(id);
sceneService.deleteById(id); sceneRepository.deleteById(id);
} }
/** /**

View File

@ -1,6 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchableSaveRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchableSaveRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
@ -24,10 +23,11 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/securityCamera") @RequestMapping("/securityCamera")
public class SecurityCameraController { public class SecurityCameraController {
@Autowired DeviceService deviceService; @Autowired private DeviceService deviceService;
@Autowired SecurityCameraRepository securityCameraService; @Autowired private SecurityCameraRepository securityCameraService;
@Autowired private SceneRepository sceneRepository; @Autowired private SceneRepository sceneRepository;
@Autowired private StateRepository<State<?>> stateRepository; @Autowired private StateRepository<State<?>> stateRepository;
@Autowired private RoomRepository roomRepository;
private SecurityCamera save( private SecurityCamera save(
SecurityCamera newSC, SwitchableSaveRequest sc, final Principal principal) { SecurityCamera newSC, SwitchableSaveRequest sc, final Principal principal) {
@ -40,7 +40,9 @@ public class SecurityCameraController {
@PostMapping @PostMapping
public SecurityCamera create( public SecurityCamera create(
@Valid @RequestBody SwitchableSaveRequest sc, final Principal principal) { @Valid @RequestBody SwitchableSaveRequest sc, final Principal principal)
throws NotFoundException {
deviceService.throwIfRoomNotOwned(sc.getRoomId(), principal.getName());
return save(new SecurityCamera(), sc, principal); return save(new SecurityCamera(), sc, principal);
} }

View File

@ -1,6 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SensorSaveRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SensorSaveRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
@ -29,7 +28,10 @@ public class SensorController {
@Autowired private SensorService sensorService; @Autowired private SensorService sensorService;
@PostMapping @PostMapping
public Sensor create(@Valid @RequestBody SensorSaveRequest s, final Principal principal) { public Sensor create(@Valid @RequestBody SensorSaveRequest s, final Principal principal)
throws NotFoundException {
deviceService.throwIfRoomNotOwned(s.getRoomId(), principal.getName());
Sensor newSensor = new Sensor(); Sensor newSensor = new Sensor();
newSensor.setSensor(s.getSensor()); newSensor.setSensor(s.getSensor());
newSensor.setName(s.getName()); newSensor.setName(s.getName());

View File

@ -1,6 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchableSaveRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchableSaveRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
@ -32,8 +31,9 @@ public class SmartPlugController {
} }
@PostMapping @PostMapping
public SmartPlug create( public SmartPlug create(@Valid @RequestBody SwitchableSaveRequest sp, final Principal principal)
@Valid @RequestBody SwitchableSaveRequest sp, final Principal principal) { throws NotFoundException {
deviceService.throwIfRoomNotOwned(sp.getRoomId(), principal.getName());
return save(new SmartPlug(), sp, principal); return save(new SmartPlug(), sp, principal);
} }

View File

@ -1,13 +1,12 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchOperationRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchOperationRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService;
import java.security.Principal; import java.security.Principal;
import java.util.*; import java.util.List;
import javax.validation.Valid; import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.*; import org.springframework.boot.autoconfigure.*;
@ -36,12 +35,12 @@ public class SwitchController extends InputDeviceConnectionController<Switch, Sw
super(inputRepository, outputRepository, Switchable.SWITCH_SWITCHABLE_CONNECTOR); super(inputRepository, outputRepository, Switchable.SWITCH_SWITCHABLE_CONNECTOR);
this.deviceService = deviceService; this.deviceService = deviceService;
this.switchRepository = inputRepository; this.switchRepository = inputRepository;
this.switchableRepository = outputRepository;
} }
@PostMapping @PostMapping
public Switch create( public Switch create(@Valid @RequestBody GenericDeviceSaveReguest s, final Principal principal)
@Valid @RequestBody GenericDeviceSaveReguest s, final Principal principal) { throws NotFoundException {
deviceService.throwIfRoomNotOwned(s.getRoomId(), principal.getName());
Switch newSwitch = new Switch(); Switch newSwitch = new Switch();
newSwitch.setName(s.getName()); newSwitch.setName(s.getName());
newSwitch.setRoomId(s.getRoomId()); newSwitch.setRoomId(s.getRoomId());
@ -50,7 +49,7 @@ public class SwitchController extends InputDeviceConnectionController<Switch, Sw
} }
@PutMapping("/operate") @PutMapping("/operate")
public Set<Switchable> operate( public List<Switchable> operate(
@Valid @RequestBody final SwitchOperationRequest sr, final Principal principal) @Valid @RequestBody final SwitchOperationRequest sr, final Principal principal)
throws NotFoundException { throws NotFoundException {
final Switch s = final Switch s =
@ -70,9 +69,8 @@ public class SwitchController extends InputDeviceConnectionController<Switch, Sw
break; break;
} }
switchableRepository.saveAll(s.getOutputs()); deviceService.saveAsOwner(s, principal.getName());
return deviceService.saveAllAsOwner(s.getOutputs(), principal.getName(), false);
return s.getOutputs();
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")

View File

@ -37,8 +37,9 @@ public class ThermostatController {
} }
@PostMapping @PostMapping
public Thermostat create( public Thermostat create(@Valid @RequestBody ThermostatSaveRequest t, final Principal principal)
@Valid @RequestBody ThermostatSaveRequest t, final Principal principal) { throws NotFoundException {
deviceService.throwIfRoomNotOwned(t.getRoomId(), principal.getName());
return save(new Thermostat(), t, principal); return save(new Thermostat(), t, principal);
} }

View File

@ -1,6 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
public class GuestPermissionsRequest { public class GuestPermissionsRequest {
private boolean cameraEnabled; private boolean cameraEnabled;

View File

@ -0,0 +1,41 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
import io.swagger.annotations.ApiModelProperty;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
@Entity
public class Automation {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false, unique = true)
@ApiModelProperty(hidden = true)
private long id;
@OneToMany(mappedBy = "automation", orphanRemoval = true)
@GsonExclude
private Set<Trigger<?>> triggers = new HashSet<>();
@OneToMany(mappedBy = "automation", cascade = CascadeType.REMOVE)
@GsonExclude
private Set<ScenePriority> scenes = new HashSet<>();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Set<Trigger<?>> getStates() {
return triggers;
}
public Set<ScenePriority> getScenes() {
return scenes;
}
}

View File

@ -0,0 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import org.springframework.data.repository.CrudRepository;
public interface AutomationRepository extends CrudRepository<Automation, Long> {}

View File

@ -0,0 +1,31 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class BooleanTrigger<D extends Device & BooleanTriggerable> extends Trigger<D> {
@Column(name = "switchable_on")
private boolean on;
public boolean isOn() {
return on;
}
public void setOn(boolean on) {
this.on = on;
}
public boolean check(boolean on) {
return this.on == on;
}
@Override
public boolean triggered() {
return getDevice().readTriggerState() == isOn();
}
}

View File

@ -0,0 +1,4 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
public interface BooleanTriggerRepository
extends TriggerRepository<BooleanTrigger<? extends Device>> {}

View File

@ -0,0 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
public interface BooleanTriggerable {
boolean readTriggerState();
}

View File

@ -36,6 +36,11 @@ public abstract class Device {
@SocketGsonExclude @SocketGsonExclude
private Room room; private Room room;
@OneToMany(mappedBy = "device", orphanRemoval = true)
@GsonExclude
// @SocketGsonExclude
private Set<Trigger<? extends Device>> triggers = new HashSet<>();
/** /**
* The room this device belongs in, as a foreign key id. To use when updating and inserting from * The room this device belongs in, as a foreign key id. To use when updating and inserting from
* a REST call. * a REST call.

View File

@ -9,7 +9,7 @@ import javax.validation.constraints.NotNull;
@Entity @Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Dimmable extends Switchable { public class Dimmable extends Switchable implements RangeTriggerable {
public static final Connector<KnobDimmer, Dimmable> KNOB_DIMMER_DIMMABLE_CONNECTOR = public static final Connector<KnobDimmer, Dimmable> KNOB_DIMMER_DIMMABLE_CONNECTOR =
Connector.basic(KnobDimmer::getOutputs, Dimmable::getDimmers); Connector.basic(KnobDimmer::getOutputs, Dimmable::getDimmers);
@ -85,4 +85,9 @@ public class Dimmable extends Switchable {
newState.setIntensity(getIntensity()); newState.setIntensity(getIntensity());
return newState; return newState;
} }
@Override
public double readTriggerState() {
return intensity;
}
} }

View File

@ -1,7 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude; import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import javax.persistence.*; import javax.persistence.*;
@ -19,8 +18,7 @@ public abstract class Dimmer extends InputDevice {
@JoinTable( @JoinTable(
name = "dimmer_dimmable", name = "dimmer_dimmable",
joinColumns = @JoinColumn(name = "dimmer_id"), joinColumns = @JoinColumn(name = "dimmer_id"),
inverseJoinColumns = @JoinColumn(name = "dimmable_id") inverseJoinColumns = @JoinColumn(name = "dimmable_id"))
)
private Set<Dimmable> dimmables = new HashSet<>(); private Set<Dimmable> dimmables = new HashSet<>();
/** /**

View File

@ -8,10 +8,9 @@ import javax.persistence.Entity;
* value, like a knob) * value, like a knob)
*/ */
@Entity @Entity
public class KnobDimmer extends Dimmer { public class KnobDimmer extends Dimmer implements RangeTriggerable {
@Column @Column private int intensity = 0;
Integer intensity = 0;
public KnobDimmer() { public KnobDimmer() {
super("knobDimmer"); super("knobDimmer");
@ -28,4 +27,9 @@ public class KnobDimmer extends Dimmer {
dl.setIntensity(intensity); dl.setIntensity(intensity);
} }
} }
@Override
public double readTriggerState() {
return intensity;
}
} }

View File

@ -5,7 +5,7 @@ import javax.persistence.Entity;
/** Represents a motion sensor device */ /** Represents a motion sensor device */
@Entity @Entity
public class MotionSensor extends InputDevice { public class MotionSensor extends InputDevice implements BooleanTriggerable {
@Column(nullable = false) @Column(nullable = false)
private boolean detected; private boolean detected;
@ -21,4 +21,9 @@ public class MotionSensor extends InputDevice {
public MotionSensor() { public MotionSensor() {
super("motionSensor"); super("motionSensor");
} }
@Override
public boolean readTriggerState() {
return detected;
}
} }

View File

@ -0,0 +1,68 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import com.google.gson.annotations.SerializedName;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.validation.constraints.NotNull;
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class RangeTrigger<D extends Device & RangeTriggerable> extends Trigger<D> {
@Override
public boolean triggered() {
double value = getDevice().readTriggerState();
switch (operator) {
case EQUAL:
return value == range;
case LESS:
return value < range;
case GREATER:
return value > range;
case GREATER_EQUAL:
return value >= range;
case LESS_EQUAL:
return value <= range;
}
throw new IllegalStateException();
}
enum Operator {
@SerializedName("EQUAL")
EQUAL,
@SerializedName("LESS")
LESS,
@SerializedName("GREATER")
GREATER,
@SerializedName("LESS_EQUAL")
LESS_EQUAL,
@SerializedName("GREATER_EQUAL")
GREATER_EQUAL
}
@NotNull
@Column(nullable = false)
private Operator operator;
@NotNull
@Column(nullable = false)
private double range;
public Operator getOperator() {
return operator;
}
public void setOperator(Operator operator) {
this.operator = operator;
}
public double getRange() {
return range;
}
public void setRange(Float range) {
this.range = range;
}
}

View File

@ -0,0 +1,3 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
public interface RangeTriggerRepository extends TriggerRepository<RangeTrigger<? extends Device>> {}

View File

@ -0,0 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
public interface RangeTriggerable {
double readTriggerState();
}

View File

@ -6,7 +6,7 @@ import javax.validation.constraints.NotNull;
/** Represents a standard non-dimmable light */ /** Represents a standard non-dimmable light */
@Entity @Entity
public class RegularLight extends Switchable { public class RegularLight extends Switchable implements BooleanTriggerable {
/** Whether the light is on or not */ /** Whether the light is on or not */
@Column(name = "light_on", nullable = false) @Column(name = "light_on", nullable = false)
@ -27,4 +27,9 @@ public class RegularLight extends Switchable {
public void setOn(boolean on) { public void setOn(boolean on) {
this.on = on; this.on = on;
} }
@Override
public boolean readTriggerState() {
return on;
}
} }

View File

@ -1,5 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
@ -15,4 +16,10 @@ public interface RoomRepository extends CrudRepository<Room, Long> {
*/ */
@Query("SELECT r FROM Room r JOIN r.user u WHERE r.id = ?1 AND u.username = ?2") @Query("SELECT r FROM Room r JOIN r.user u WHERE r.id = ?1 AND u.username = ?2")
Optional<Room> findByIdAndUsername(Long id, String username); Optional<Room> findByIdAndUsername(Long id, String username);
@Query("SELECT r FROM Room r JOIN r.user u WHERE u.username = ?1")
List<Room> findByUsername(String username);
@Query("SELECT r FROM Room r JOIN r.user u WHERE u.id = ?1")
List<Room> findByUserId(Long hostId);
} }

View File

@ -25,15 +25,15 @@ public class Scene {
@GsonExclude @GsonExclude
private User user; private User user;
@OneToMany(mappedBy = "scene", orphanRemoval = true)
@GsonExclude
private Set<State<?>> states = new HashSet<>();
@NotNull @NotNull
@Column(name = "user_id", nullable = false) @Column(name = "user_id", nullable = false)
@GsonExclude @GsonExclude
private Long userId; private Long userId;
@OneToMany(mappedBy = "scene", orphanRemoval = true)
@GsonExclude
private Set<State<?>> states = new HashSet<>();
/** The user given name of this room (e.g. 'Master bedroom') */ /** The user given name of this room (e.g. 'Master bedroom') */
@NotNull @NotNull
@Column(nullable = false) @Column(nullable = false)

View File

@ -0,0 +1,88 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PreRemove;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Entity
public class ScenePriority {
@ManyToOne
@JoinColumn(name = "automation_id", updatable = false, insertable = false)
@GsonExclude
private Automation automation;
@Column(name = "automation_id", nullable = false)
@NotNull
private Long automationId;
@NotNull
@Min(0)
@Column(nullable = false)
private Integer priority;
@ManyToOne
@JoinColumn(name = "scene_id", updatable = false, insertable = false)
@GsonExclude
private Scene scene;
@Id
@Column(name = "scene_id", nullable = false, updatable = false, unique = true)
@NotNull
private Long sceneId;
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
public Automation getAutomation() {
return automation;
}
public void setAutomation(Automation automation) {
this.automation = automation;
}
public Long getAutomationId() {
return automationId;
}
public void setAutomationId(Long automationId) {
this.automationId = automationId;
}
public Scene getScene() {
return scene;
}
public void setScene(Scene scene) {
this.scene = scene;
}
public Long getSceneId() {
return sceneId;
}
public void setSceneId(Long sceneId) {
this.sceneId = sceneId;
}
@PreRemove
public void preRemove() {
this.setAutomation(null);
this.setAutomationId(null);
this.setScene(null);
this.setSceneId(null);
}
}

View File

@ -0,0 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import org.springframework.data.repository.CrudRepository;
public interface ScenePriorityRepository extends CrudRepository<ScenePriority, Long> {}

View File

@ -5,7 +5,7 @@ import javax.persistence.Entity;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@Entity @Entity
public class SecurityCamera extends Switchable { public class SecurityCamera extends Switchable implements BooleanTriggerable {
public SecurityCamera() { public SecurityCamera() {
super("securityCamera"); super("securityCamera");
@ -33,4 +33,9 @@ public class SecurityCamera extends Switchable {
public void setOn(boolean on) { public void setOn(boolean on) {
this.on = on; this.on = on;
} }
@Override
public boolean readTriggerState() {
return on;
}
} }

View File

@ -11,7 +11,7 @@ import javax.validation.constraints.NotNull;
/** A sensor input device that measures a quantity in a continuous scale (e.g. temperature) */ /** A sensor input device that measures a quantity in a continuous scale (e.g. temperature) */
@Entity @Entity
public class Sensor extends InputDevice { public class Sensor extends InputDevice implements RangeTriggerable {
public static final Map<SensorType, BigDecimal> TYPICAL_VALUES = public static final Map<SensorType, BigDecimal> TYPICAL_VALUES =
Map.of( Map.of(
@ -19,6 +19,11 @@ public class Sensor extends InputDevice {
SensorType.HUMIDITY, new BigDecimal(40.0), SensorType.HUMIDITY, new BigDecimal(40.0),
SensorType.LIGHT, new BigDecimal(1000)); SensorType.LIGHT, new BigDecimal(1000));
@Override
public double readTriggerState() {
return value.doubleValue();
}
/** Type of sensor, i.e. of the thing the sensor measures. */ /** Type of sensor, i.e. of the thing the sensor measures. */
public enum SensorType { public enum SensorType {
/** A sensor that measures temperature in degrees celsius */ /** A sensor that measures temperature in degrees celsius */

View File

@ -1,6 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import com.google.common.base.Objects;
import java.math.BigDecimal; import java.math.BigDecimal;
import javax.persistence.Column; import javax.persistence.Column;
@ -9,7 +8,7 @@ import javax.validation.constraints.NotNull;
/** A smart plug that can be turned either on or off */ /** A smart plug that can be turned either on or off */
@Entity @Entity
public class SmartPlug extends Switchable { public class SmartPlug extends Switchable implements BooleanTriggerable {
/** The average consumption of an active plug when on in Watt */ /** The average consumption of an active plug when on in Watt */
public static final Double AVERAGE_CONSUMPTION_KW = 200.0; public static final Double AVERAGE_CONSUMPTION_KW = 200.0;
@ -46,4 +45,9 @@ public class SmartPlug extends Switchable {
public SmartPlug() { public SmartPlug() {
super("smartPlug"); super("smartPlug");
} }
@Override
public boolean readTriggerState() {
return on;
}
} }

View File

@ -1,22 +1,20 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude; import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import javax.persistence.*; import javax.persistence.*;
/** A switch input device */ /** A switch input device */
@Entity @Entity
public class Switch extends InputDevice { public class Switch extends InputDevice implements BooleanTriggerable {
@ManyToMany(cascade = CascadeType.DETACH) @ManyToMany(cascade = CascadeType.DETACH)
@GsonExclude @GsonExclude
@JoinTable( @JoinTable(
name = "switch_switchable", name = "switch_switchable",
joinColumns = @JoinColumn(name = "switch_id"), joinColumns = @JoinColumn(name = "switch_id"),
inverseJoinColumns = @JoinColumn(name = "switchable_id") inverseJoinColumns = @JoinColumn(name = "switchable_id"))
)
private Set<Switchable> switchables = new HashSet<>(); private Set<Switchable> switchables = new HashSet<>();
/** The state of this switch */ /** The state of this switch */
@ -57,4 +55,9 @@ public class Switch extends InputDevice {
public Set<Switchable> getOutputs() { public Set<Switchable> getOutputs() {
return switchables; return switchables;
} }
@Override
public boolean readTriggerState() {
return on;
}
} }

View File

@ -1,15 +1,19 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.persistence.Transient; import javax.persistence.Transient;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
/** A thermostat capable of controlling cooling and heating. */ /** A thermostat capable of controlling cooling and heating. */
@Entity @Entity
public class Thermostat extends Switchable { public class Thermostat extends Switchable implements BooleanTriggerable {
@Override @Override
public boolean isOn() { public boolean isOn() {
@ -24,6 +28,7 @@ public class Thermostat extends Switchable {
/** /**
* Computes the new thermostat state, for when the thermostat is on; * Computes the new thermostat state, for when the thermostat is on;
*
* @return true if the state changed, false if not; * @return true if the state changed, false if not;
*/ */
public boolean computeState() { public boolean computeState() {
@ -49,6 +54,11 @@ public class Thermostat extends Switchable {
return true; return true;
} }
@Override
public boolean readTriggerState() {
return mode != Mode.OFF;
}
public enum Mode { public enum Mode {
@SerializedName("OFF") @SerializedName("OFF")
OFF, OFF,
@ -75,6 +85,10 @@ public class Thermostat extends Switchable {
@Column private boolean useExternalSensors = false; @Column private boolean useExternalSensors = false;
@OneToMany(mappedBy = "scene", orphanRemoval = true)
@GsonExclude
private Set<BooleanTrigger<? extends Device>> triggers = new HashSet<>();
/** Creates a thermostat with a temperature sensor and its initial OFF state */ /** Creates a thermostat with a temperature sensor and its initial OFF state */
public Thermostat() { public Thermostat() {
super("thermostat"); super("thermostat");

View File

@ -0,0 +1,99 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
import io.swagger.annotations.ApiModelProperty;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PreRemove;
import javax.validation.constraints.NotNull;
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Trigger<D extends Device> {
public abstract boolean triggered();
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false, unique = true)
@ApiModelProperty(hidden = true)
private long id;
@ManyToOne(targetEntity = OutputDevice.class)
@JoinColumn(name = "device_id", updatable = false, insertable = false)
@GsonExclude
private D device;
/**
* The device this trigger belongs to, as a foreign key id. To use when updating and inserting
* from a REST call.
*/
@Column(name = "device_id", nullable = false)
@NotNull
private Long deviceId;
@ManyToOne
@JoinColumn(name = "automation_id", updatable = false, insertable = false)
@GsonExclude
private Automation automation;
@Column(name = "automation_id", nullable = false)
@NotNull
private Long automationId;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public D getDevice() {
return device;
}
public void setDevice(D device) {
this.device = device;
}
public Long getDeviceId() {
return deviceId;
}
public void setDeviceId(Long deviceId) {
this.deviceId = deviceId;
}
public Automation getAutomation() {
return automation;
}
public void setAutomation(Automation automation) {
this.automation = automation;
}
public Long getAutomationId() {
return automationId;
}
public void setAutomationId(Long automationId) {
this.automationId = automationId;
}
@PreRemove
public void removeDeviceAndScene() {
this.setDevice(null);
this.setDeviceId(null);
this.setAutomation(null);
this.setAutomationId(null);
}
}

View File

@ -0,0 +1,10 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
public interface TriggerRepository<T extends Trigger<?>> extends CrudRepository<T, Long> {
List<T> findAllByDeviceId(@Param("deviceId") long deviceId);
}

View File

@ -1,10 +1,17 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.util.*; import java.util.*;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, Long> { public interface UserRepository extends CrudRepository<User, Long> {
User findByUsername(String username); User findByUsername(String username);
@Query("SELECT u FROM #{#entityName} u JOIN FETCH u.guests WHERE u.username = ?1")
User findByUsernameFetchGuests(String username);
@Query("SELECT u FROM #{#entityName} u JOIN FETCH u.guests WHERE u.id = ?1")
User findByIdFetchGuests(Long id);
User findByEmailIgnoreCase(String email); User findByEmailIgnoreCase(String email);
} }

View File

@ -1,7 +1,7 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.scheduled; package ch.usi.inf.sa4.sanmarinoes.smarthut.scheduled;
import ch.usi.inf.sa4.sanmarinoes.smarthut.controller.MotionSensorController;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.MotionSensorService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.SensorService; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.SensorService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.ThermostatService; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.ThermostatService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint; import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
@ -30,7 +30,7 @@ public class UpdateTasks {
@Autowired private ThermostatService thermostatService; @Autowired private ThermostatService thermostatService;
@Autowired private MotionSensorController motionSensorController; @Autowired private MotionSensorService motionSensorService;
@Autowired private SensorSocketEndpoint sensorSocketEndpoint; @Autowired private SensorSocketEndpoint sensorSocketEndpoint;
@ -55,14 +55,18 @@ public class UpdateTasks {
StreamSupport.stream(motionSensorRepository.findAll().spliterator(), true) StreamSupport.stream(motionSensorRepository.findAll().spliterator(), true)
.forEach( .forEach(
sensor -> { sensor -> {
motionSensorController.updateDetectionFromMotionSensor(sensor, true); final User owner = motionSensorRepository.findUser(sensor.getId());
motionSensorService.updateDetectionFromMotionSensor(
sensor, true, owner.getUsername());
CompletableFuture.delayedExecutor( CompletableFuture.delayedExecutor(
(long) (Math.random() * 2000), TimeUnit.MILLISECONDS) (long) (Math.random() * 2000), TimeUnit.MILLISECONDS)
.execute( .execute(
() -> () ->
motionSensorController motionSensorService
.updateDetectionFromMotionSensor( .updateDetectionFromMotionSensor(
sensor, false)); sensor,
false,
owner.getUsername()));
}); });
} }

View File

@ -5,6 +5,8 @@ import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint; import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
import java.util.Collection;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -12,14 +14,44 @@ import org.springframework.stereotype.Component;
@Component @Component
public class DeviceService { public class DeviceService {
// FIXME: TO BE MERGED WITH USER STORY 5 (MATTEO'S STUFF)
@Autowired private DeviceRepository<Device> deviceRepository; @Autowired private DeviceRepository<Device> deviceRepository;
@Autowired private AutomationRepository automationRepository;
@Autowired private SceneRepository sceneRepository;
@Autowired private SceneService sceneService;
@Autowired private TriggerRepository<Trigger<? extends Device>> triggerRepository;
@Autowired private RoomRepository roomRepository; @Autowired private RoomRepository roomRepository;
@Autowired private UserRepository userRepository; @Autowired private UserRepository userRepository;
@Autowired private SensorSocketEndpoint endpoint; @Autowired private SensorSocketEndpoint endpoint;
@Autowired private ThermostatService thermostatService; @Autowired private ThermostatService thermostatService;
public void throwIfRoomNotOwned(Long roomId, String username) throws NotFoundException {
roomRepository.findByIdAndUsername(roomId, username).orElseThrow(NotFoundException::new);
}
public void triggerTriggers(Device device) {
final long deviceId = device.getId();
List<Trigger<? extends Device>> triggers = triggerRepository.findAllByDeviceId(deviceId);
triggers.stream()
.filter(Trigger::triggered)
.map(Trigger::getAutomationId)
.map(t -> automationRepository.findById(t).orElseThrow(IllegalStateException::new))
.distinct()
.map(Automation::getScenes)
.flatMap(Collection::stream)
.distinct()
.sorted(Comparator.comparing(ScenePriority::getPriority))
.map(
t ->
sceneRepository
.findById(t.getSceneId())
.orElseThrow(IllegalStateException::new))
.forEach(sceneService::apply);
}
public List<Device> findAll(Long hostId, String username) throws NotFoundException { public List<Device> findAll(Long hostId, String username) throws NotFoundException {
return findAll(null, hostId, username); return findAll(null, hostId, username);
} }
@ -72,10 +104,9 @@ public class DeviceService {
public <T extends Device> T saveAsGuest(T device, String guestUsername, Long hostId) public <T extends Device> T saveAsGuest(T device, String guestUsername, Long hostId)
throws NotFoundException { throws NotFoundException {
device = deviceRepository.save(device);
final User currentUser = userRepository.findByUsername(guestUsername); final User currentUser = userRepository.findByUsername(guestUsername);
final User host = userRepository.findById(hostId).orElseThrow(NotFoundException::new); final User host = userRepository.findByIdFetchGuests(hostId);
if (host == null) throw new NotFoundException();
final Set<User> guests = Set.copyOf(host.getGuests()); final Set<User> guests = Set.copyOf(host.getGuests());
// We're telling the host that a guest has modified a device. Therefore, fromGuest becomes // We're telling the host that a guest has modified a device. Therefore, fromGuest becomes
@ -100,10 +131,8 @@ public class DeviceService {
return device; return device;
} }
public <T extends Device> T saveAsOwner(T device, String username) { private void propagateUpdateAsOwner(Device device, String username) {
device = deviceRepository.save(device); final User user = userRepository.findByUsernameFetchGuests(username);
final User user = userRepository.findByUsername(username);
final Set<User> guests = user.getGuests(); final Set<User> guests = user.getGuests();
// make sure we're broadcasting from host // make sure we're broadcasting from host
device.setFromHost(true); device.setFromHost(true);
@ -112,10 +141,35 @@ public class DeviceService {
// broadcast to endpoint the object device, with receiving user set to guest // broadcast to endpoint the object device, with receiving user set to guest
endpoint.queueDeviceUpdate(device, guest); endpoint.queueDeviceUpdate(device, guest);
} }
}
public <T extends Device> List<T> saveAllAsOwner(
Iterable<T> devices, String username, boolean fromScene) {
devices = deviceRepository.saveAll(devices);
devices.forEach((d) -> propagateUpdateAsOwner(d, username));
if (!fromScene) {
devices.forEach(this::triggerTriggers);
}
return toList(devices);
}
public <T extends Device> T saveAsOwner(T device, String username, boolean fromScene) {
device = deviceRepository.save(device);
propagateUpdateAsOwner(device, username);
if (!fromScene) {
triggerTriggers(device);
}
return device; return device;
} }
public <T extends Device> T saveAsOwner(T device, String username) {
return saveAsOwner(device, username, false);
}
public void delete(Long id, String username) throws NotFoundException { public void delete(Long id, String username) throws NotFoundException {
Device device = Device device =
deviceRepository deviceRepository
@ -123,7 +177,7 @@ public class DeviceService {
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
deviceRepository.delete(device); deviceRepository.delete(device);
final User user = userRepository.findByUsername(username); final User user = userRepository.findByUsernameFetchGuests(username);
final Set<User> guests = user.getGuests(); final Set<User> guests = user.getGuests();
device.setFromHost(true); device.setFromHost(true);

View File

@ -0,0 +1,33 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.service;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensor;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensorRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MotionSensorService {
@Autowired private SensorSocketEndpoint sensorSocketEndpoint;
@Autowired private DeviceService deviceService;
@Autowired private MotionSensorRepository motionSensorRepository;
/**
* Updates detection status of given motion sensor and propagates update throgh socket
*
* @param sensor the motion sensor to update
* @param detected the new detection status
* @return the updated motion sensor
*/
public MotionSensor updateDetectionFromMotionSensor(
MotionSensor sensor, boolean detected, String username) {
sensor.setDetected(detected);
final MotionSensor toReturn = deviceService.saveAsOwner(sensor, username);
sensorSocketEndpoint.queueDeviceUpdate(
sensor, motionSensorRepository.findUser(sensor.getId()));
return toReturn;
}
}

View File

@ -0,0 +1,28 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.service;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Device;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DeviceRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Scene;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.State;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class SceneService {
@Autowired private DeviceRepository<Device> deviceRepository;
public List<Device> apply(Scene newScene) {
final List<Device> updated = new ArrayList<>();
for (final State<?> s : newScene.getStates()) {
s.apply();
updated.add(s.getDevice());
}
deviceRepository.saveAll(updated);
return updated;
}
}

View File

@ -12,6 +12,8 @@ public class SensorService {
@Autowired private SensorRepository sensorRepository; @Autowired private SensorRepository sensorRepository;
@Autowired private DeviceService deviceService;
@Autowired private ThermostatService thermostatService; @Autowired private ThermostatService thermostatService;
@Autowired private SensorSocketEndpoint endpoint; @Autowired private SensorSocketEndpoint endpoint;
@ -38,10 +40,10 @@ public class SensorService {
*/ */
public Sensor updateValueFromSensor(Sensor sensor, BigDecimal value) { public Sensor updateValueFromSensor(Sensor sensor, BigDecimal value) {
sensor.setValue(value); sensor.setValue(value);
final Sensor toReturn = sensorRepository.save(sensor); sensor =
deviceService.saveAsOwner(
sensor, sensorRepository.findUser(sensor.getId()).getUsername());
endpoint.queueDeviceUpdate(sensor, sensorRepository.findUser(sensor.getId())); endpoint.queueDeviceUpdate(sensor, sensorRepository.findUser(sensor.getId()));
return sensor;
return toReturn;
} }
} }

View File

@ -16,6 +16,8 @@ public class ThermostatService {
@Autowired private SensorSocketEndpoint endpoint; @Autowired private SensorSocketEndpoint endpoint;
@Autowired private DeviceService deviceService;
@Autowired private ThermostatRepository thermostatRepository; @Autowired private ThermostatRepository thermostatRepository;
private void randomJitter(Thermostat thermostat) { private void randomJitter(Thermostat thermostat) {
@ -28,7 +30,8 @@ public class ThermostatService {
private void updateValueForThermostat(Thermostat thermostat, BigDecimal value) { private void updateValueForThermostat(Thermostat thermostat, BigDecimal value) {
thermostat.setInternalSensorTemperature(value); thermostat.setInternalSensorTemperature(value);
thermostatRepository.save(thermostat); deviceService.saveAsOwner(
thermostat, thermostatRepository.findUser(thermostat.getId()).getUsername());
} }
public void fakeUpdateAll() { public void fakeUpdateAll() {
@ -51,7 +54,7 @@ public class ThermostatService {
boolean shouldUpdate = this.computeState(t); boolean shouldUpdate = this.computeState(t);
if (shouldUpdate) { if (shouldUpdate) {
thermostatRepository.save(t); deviceService.saveAsOwner(t, thermostatRepository.findUser(t.getId()).getUsername());
endpoint.queueDeviceUpdate(t, thermostatRepository.findUser(t.getId())); endpoint.queueDeviceUpdate(t, thermostatRepository.findUser(t.getId()));
} }
} }