Merge remote-tracking branch 'origin/dev' into dev
# Conflicts: # src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ThermostatController.java
This commit is contained in:
commit
912d81e84f
40 changed files with 536 additions and 274 deletions
|
@ -4,6 +4,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.AutomationFastUpdateRequest;
|
|||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableState;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.State;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.SwitchableState;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Trigger;
|
||||
import com.google.gson.*;
|
||||
import java.lang.reflect.Type;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
@ -27,6 +28,7 @@ public class GsonConfig {
|
|||
private static GsonBuilder configureBuilder() {
|
||||
final GsonBuilder builder = new GsonBuilder();
|
||||
builder.registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter());
|
||||
@SuppressWarnings({"rawTypes"})
|
||||
RuntimeTypeAdapterFactory<State> runtimeTypeAdapterFactory =
|
||||
RuntimeTypeAdapterFactory.of(State.class, "kind")
|
||||
.registerSubtype(SwitchableState.class, "switchableState")
|
||||
|
@ -43,6 +45,10 @@ public class GsonConfig {
|
|||
"rangeTrigger");
|
||||
builder.registerTypeAdapterFactory(runtimeTypeAdapterFactory);
|
||||
builder.registerTypeAdapterFactory(runtimeTypeAdapterFactoryII);
|
||||
builder.registerTypeAdapter(
|
||||
Trigger.class,
|
||||
(JsonSerializer<Trigger<?>>)
|
||||
(src, typeOfSrc, context) -> context.serialize((Object) src));
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,11 @@ public class ButtonDimmerController
|
|||
this.buttonDimmerRepository = inputRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ButtonDimmer findById(@PathVariable("id") long id) throws NotFoundException {
|
||||
return buttonDimmerRepository.findById(id).orElseThrow(NotFoundException::new);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ButtonDimmer create(
|
||||
@Valid @RequestBody final GenericDeviceSaveReguest bd, final Principal principal)
|
||||
|
@ -62,7 +67,7 @@ public class ButtonDimmerController
|
|||
break;
|
||||
}
|
||||
|
||||
deviceService.saveAllAsOwner(buttonDimmer.getOutputs(), principal.getName(), false);
|
||||
deviceService.saveAllAsOwner(buttonDimmer.getOutputs(), principal.getName());
|
||||
|
||||
return buttonDimmer.getOutputs();
|
||||
}
|
||||
|
@ -70,6 +75,6 @@ public class ButtonDimmerController
|
|||
@DeleteMapping("/{id}")
|
||||
public void delete(@PathVariable("id") long id, final Principal principal)
|
||||
throws NotFoundException {
|
||||
deviceService.delete(id, principal.getName());
|
||||
deviceService.deleteByIdAsOwner(id, principal.getName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ public class CurtainsController {
|
|||
@DeleteMapping("/{id}")
|
||||
public void delete(@PathVariable("id") long id, final Principal principal)
|
||||
throws NotFoundException {
|
||||
deviceService.delete(id, principal.getName());
|
||||
deviceService.deleteByIdAsOwner(id, principal.getName());
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/state")
|
||||
|
|
|
@ -66,7 +66,9 @@ public class DimmableLightController extends GuestEnabledController<DimmableLigh
|
|||
*/
|
||||
@PutMapping
|
||||
public DimmableLight update(
|
||||
@Valid @RequestBody DimmableSaveRequest sp, final Principal principal, Long hostId)
|
||||
@Valid @RequestBody DimmableSaveRequest sp,
|
||||
final Principal principal,
|
||||
@RequestParam(value = "hostId", required = false) Long hostId)
|
||||
throws NotFoundException {
|
||||
|
||||
return save(
|
||||
|
@ -79,7 +81,7 @@ public class DimmableLightController extends GuestEnabledController<DimmableLigh
|
|||
@DeleteMapping("/{id}")
|
||||
public void delete(@PathVariable("id") long id, final Principal principal)
|
||||
throws NotFoundException {
|
||||
deviceService.delete(id, principal.getName());
|
||||
deviceService.deleteByIdAsOwner(id, principal.getName());
|
||||
}
|
||||
|
||||
// the full url should be: "/dimmableLight/{id}/state?sceneId={sceneId}
|
||||
|
|
|
@ -3,11 +3,16 @@ 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.dto.GuestPermissionsRequest;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GuestsUpdateRequest;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserResponse;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.EagerUserRepository;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
|
||||
import java.security.Principal;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
|
@ -18,23 +23,48 @@ import org.springframework.web.bind.annotation.*;
|
|||
@RequestMapping("/user")
|
||||
public class GuestController {
|
||||
|
||||
@Autowired private UserRepository userRepository;
|
||||
@Autowired private EagerUserRepository userRepository;
|
||||
|
||||
@GetMapping
|
||||
public List<User> findAll() {
|
||||
return toList(userRepository.findAll());
|
||||
public List<UserResponse> findAll() {
|
||||
return StreamSupport.stream(userRepository.findAll().spliterator(), false)
|
||||
.map(UserResponse::fromUser)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@PostMapping("/guest")
|
||||
public User addUserAsGuest(@RequestParam("userId") long id, final Principal principal)
|
||||
@GetMapping("/hosts")
|
||||
public List<UserResponse> findHosts(final Principal principal) {
|
||||
final User u = userRepository.findByUsername(principal.getName());
|
||||
return u.getHosts().stream().map(UserResponse::fromUser).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@GetMapping("/guests")
|
||||
public List<UserResponse> findGuests(final Principal principal) {
|
||||
final User u = userRepository.findByUsername(principal.getName());
|
||||
return u.getGuests().stream().map(UserResponse::fromUser).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@PutMapping("/guests")
|
||||
public List<User> setGuests(
|
||||
@RequestBody @Valid GuestsUpdateRequest g, final Principal principal)
|
||||
throws NotFoundException {
|
||||
User guest = userRepository.findById(id).orElseThrow(NotFoundException::new);
|
||||
Iterable<User> guests = userRepository.findAllById(g.ids);
|
||||
User host = userRepository.findByUsername(principal.getName());
|
||||
|
||||
for (final User oldGuest : host.getGuests()) {
|
||||
oldGuest.getHosts().remove(host);
|
||||
}
|
||||
|
||||
final Set<User> oldGuests = Set.copyOf(host.getGuests());
|
||||
|
||||
for (final User guest : guests) {
|
||||
host.addGuest(guest);
|
||||
guest.addHost(host);
|
||||
userRepository.save(guest);
|
||||
return userRepository.save(host);
|
||||
}
|
||||
|
||||
userRepository.saveAll(oldGuests);
|
||||
userRepository.save(host);
|
||||
return toList(userRepository.saveAll(guests));
|
||||
}
|
||||
|
||||
@PutMapping("/permissions")
|
||||
|
@ -44,16 +74,4 @@ public class GuestController {
|
|||
currentUser.setCameraEnabled(g.isCameraEnabled());
|
||||
return userRepository.save(currentUser);
|
||||
}
|
||||
|
||||
@DeleteMapping("/guest")
|
||||
public void removeUserAsGuest(@RequestParam("userId") long id, final Principal principal)
|
||||
throws NotFoundException {
|
||||
User guest = userRepository.findById(id).orElseThrow(NotFoundException::new);
|
||||
User host = userRepository.findByUsername(principal.getName());
|
||||
|
||||
host.removeGuest(guest);
|
||||
guest.getHosts().remove(host);
|
||||
userRepository.save(host);
|
||||
userRepository.save(guest);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ public abstract class InputDeviceConnectionController<
|
|||
connector.connect(pair.input, o, true);
|
||||
}
|
||||
|
||||
deviceService.saveAllAsOwner(pair.outputs, username, false);
|
||||
deviceService.saveAllAsOwner(pair.outputs, username);
|
||||
return pair.input.getOutputs();
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ public abstract class InputDeviceConnectionController<
|
|||
connector.connect(pair.input, o, false);
|
||||
}
|
||||
|
||||
deviceService.saveAllAsOwner(pair.outputs, username, false);
|
||||
deviceService.saveAllAsOwner(pair.outputs, username);
|
||||
return pair.input.getOutputs();
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,11 @@ public class KnobDimmerController extends InputDeviceConnectionController<KnobDi
|
|||
this.knobDimmerRepository = inputRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public KnobDimmer findById(@PathVariable("id") long id) throws NotFoundException {
|
||||
return knobDimmerRepository.findById(id).orElseThrow(NotFoundException::new);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public KnobDimmer create(
|
||||
@Valid @RequestBody GenericDeviceSaveReguest kd, final Principal principal)
|
||||
|
@ -49,7 +54,7 @@ public class KnobDimmerController extends InputDeviceConnectionController<KnobDi
|
|||
.orElseThrow(NotFoundException::new);
|
||||
|
||||
dimmer.setLightIntensity(bd.getIntensity());
|
||||
deviceService.saveAllAsOwner(dimmer.getOutputs(), principal.getName(), false);
|
||||
deviceService.saveAllAsOwner(dimmer.getOutputs(), principal.getName());
|
||||
|
||||
return dimmer.getOutputs();
|
||||
}
|
||||
|
@ -57,6 +62,6 @@ public class KnobDimmerController extends InputDeviceConnectionController<KnobDi
|
|||
@DeleteMapping("/{id}")
|
||||
public void delete(@PathVariable("id") long id, final Principal principal)
|
||||
throws NotFoundException {
|
||||
deviceService.delete(id, principal.getName());
|
||||
deviceService.deleteByIdAsOwner(id, principal.getName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,6 @@ public class MotionSensorController {
|
|||
@DeleteMapping("/{id}")
|
||||
public void delete(@PathVariable("id") long id, final Principal principal)
|
||||
throws NotFoundException {
|
||||
deviceService.delete(id, principal.getName());
|
||||
deviceService.deleteByIdAsOwner(id, principal.getName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,9 @@ public class RegularLightController extends GuestEnabledController<RegularLight>
|
|||
|
||||
@PutMapping
|
||||
public RegularLight update(
|
||||
@Valid @RequestBody SwitchableSaveRequest rl, final Principal principal, Long hostId)
|
||||
@Valid @RequestBody SwitchableSaveRequest rl,
|
||||
final Principal principal,
|
||||
@RequestParam(value = "hostId", required = false) Long hostId)
|
||||
throws NotFoundException {
|
||||
return save(
|
||||
fetchIfOwnerOrGuest(principal, rl.getId(), hostId),
|
||||
|
@ -92,7 +94,7 @@ public class RegularLightController extends GuestEnabledController<RegularLight>
|
|||
@DeleteMapping("/{id}")
|
||||
public void delete(@PathVariable("id") long id, final Principal principal)
|
||||
throws NotFoundException {
|
||||
deviceService.delete(id, principal.getName());
|
||||
deviceService.deleteByIdAsOwner(id, principal.getName());
|
||||
}
|
||||
|
||||
// the full url should be: "/regularLight/{id}/state?sceneId={sceneId}
|
||||
|
|
|
@ -75,7 +75,7 @@ public class RoomController {
|
|||
final String username = principal.getName();
|
||||
final Long userId = userRepository.findByUsername(username).getId();
|
||||
final String img = r.getImage();
|
||||
final Room.Icon icon = r.getIcon();
|
||||
final Icon icon = r.getIcon();
|
||||
|
||||
final Room newRoom = new Room();
|
||||
newRoom.setUserId(userId);
|
||||
|
@ -95,7 +95,7 @@ public class RoomController {
|
|||
.findByIdAndUsername(id, principal.getName())
|
||||
.orElseThrow(NotFoundException::new);
|
||||
final String img = r.getImage();
|
||||
final Room.Icon icon = r.getIcon();
|
||||
final Icon icon = r.getIcon();
|
||||
|
||||
if (r.getName() != null) {
|
||||
newRoom.setName(r.getName());
|
||||
|
@ -124,6 +124,11 @@ public class RoomController {
|
|||
roomRepository
|
||||
.findByIdAndUsername(id, principal.getName())
|
||||
.orElseThrow(NotFoundException::new);
|
||||
List<Device> devices = deviceService.findAll(r.getId(), null, principal.getName());
|
||||
for (Device d : devices) {
|
||||
deviceService.deleteByIdAsOwner(d.getId(), principal.getName());
|
||||
}
|
||||
|
||||
roomRepository.delete(r);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,20 +6,13 @@ 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.models.*;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.SceneService;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils;
|
||||
import java.security.Principal;
|
||||
import java.util.List;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@EnableAutoConfiguration
|
||||
|
@ -27,21 +20,20 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
public class SceneController {
|
||||
|
||||
@Autowired private SceneRepository sceneRepository;
|
||||
@Autowired private UserRepository userRepository;
|
||||
@Autowired private SceneService sceneService;
|
||||
@Autowired private UserRepository userService;
|
||||
@Autowired private StateRepository<State<?>> stateService;
|
||||
@Autowired private StateRepository<State<?>> stateRepository;
|
||||
|
||||
@GetMapping
|
||||
public List<Scene> findAll(Principal principal) {
|
||||
return toList(sceneRepository.findByUsername(principal.getName()));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public @ResponseBody Scene findById(@PathVariable("id") long id, Principal principal)
|
||||
public List<Scene> findAll(
|
||||
Principal principal, @RequestParam(value = "hostId", required = false) Long hostId)
|
||||
throws NotFoundException {
|
||||
return sceneRepository
|
||||
.findByIdAndUsername(id, principal.getName())
|
||||
.orElseThrow(NotFoundException::new);
|
||||
if (hostId == null) {
|
||||
return toList(sceneRepository.findByUsername(principal.getName()));
|
||||
} else {
|
||||
Utils.returnIfGuest(userRepository, null, hostId, principal);
|
||||
return sceneRepository.findByHostId(hostId);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
|
@ -49,26 +41,58 @@ public class SceneController {
|
|||
@Valid @RequestBody SceneSaveRequest s, final Principal principal) {
|
||||
|
||||
final String username = principal.getName();
|
||||
final Long userId = userService.findByUsername(username).getId();
|
||||
final Long userId = userRepository.findByUsername(username).getId();
|
||||
|
||||
final Scene newScene = new Scene();
|
||||
|
||||
newScene.setUserId(userId);
|
||||
newScene.setName(s.getName());
|
||||
newScene.setGuestAccessEnabled(s.isGuestAccessEnabled());
|
||||
newScene.setIcon(s.getIcon());
|
||||
|
||||
return sceneRepository.save(newScene);
|
||||
}
|
||||
|
||||
@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,
|
||||
@RequestParam(value = "hostId", required = false) Long hostId)
|
||||
throws NotFoundException {
|
||||
final Scene newScene =
|
||||
if (hostId == null) {
|
||||
return sceneService.apply(
|
||||
sceneRepository
|
||||
.findByIdAndUsername(id, principal.getName())
|
||||
.orElseThrow(NotFoundException::new),
|
||||
principal.getName(),
|
||||
false);
|
||||
} else {
|
||||
Utils.returnIfGuest(userRepository, null, hostId, principal);
|
||||
return sceneService.applyAsGuest(
|
||||
sceneRepository
|
||||
.findByIdAndUserId(id, hostId)
|
||||
.orElseThrow(NotFoundException::new),
|
||||
principal.getName(),
|
||||
hostId);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/copyFrom/{copyId}")
|
||||
public @ResponseBody List<State<?>> copy(
|
||||
@PathVariable("id") long id,
|
||||
@PathVariable("copyId") long copyId,
|
||||
final Principal principal)
|
||||
throws NotFoundException {
|
||||
final Scene scene =
|
||||
sceneRepository
|
||||
.findByIdAndUsername(id, principal.getName())
|
||||
.orElseThrow(NotFoundException::new);
|
||||
final Scene copyFrom =
|
||||
sceneRepository
|
||||
.findByIdAndUsername(copyId, principal.getName())
|
||||
.orElseThrow(NotFoundException::new);
|
||||
|
||||
return sceneService.apply(newScene);
|
||||
return sceneService.copyStates(scene, copyFrom);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
|
@ -84,6 +108,8 @@ public class SceneController {
|
|||
newScene.setName(s.getName());
|
||||
}
|
||||
|
||||
newScene.setIcon(s.getIcon());
|
||||
|
||||
newScene.setGuestAccessEnabled(s.isGuestAccessEnabled());
|
||||
|
||||
return sceneRepository.save(newScene);
|
||||
|
@ -91,7 +117,7 @@ public class SceneController {
|
|||
|
||||
@DeleteMapping("/{id}")
|
||||
public void deleteById(@PathVariable("id") long id) {
|
||||
stateService.deleteAllBySceneId(id);
|
||||
stateRepository.deleteAllBySceneId(id);
|
||||
sceneRepository.deleteById(id);
|
||||
}
|
||||
|
||||
|
@ -101,7 +127,7 @@ public class SceneController {
|
|||
*/
|
||||
@GetMapping(path = "/{sceneId}/states")
|
||||
public List<State<?>> getDevices(@PathVariable("sceneId") long sceneId) {
|
||||
Iterable<State<?>> states = stateService.findBySceneId(sceneId);
|
||||
Iterable<State<?>> states = stateRepository.findBySceneId(sceneId);
|
||||
return toList(states);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ public class SecurityCameraController {
|
|||
@DeleteMapping("/{id}")
|
||||
public void delete(@PathVariable("id") long id, final Principal principal)
|
||||
throws NotFoundException {
|
||||
deviceService.delete(id, principal.getName());
|
||||
deviceService.deleteByIdAsOwner(id, principal.getName());
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/state")
|
||||
|
|
|
@ -57,6 +57,6 @@ public class SensorController {
|
|||
@DeleteMapping("/{id}")
|
||||
public void deleteById(@PathVariable("id") long id, final Principal principal)
|
||||
throws NotFoundException {
|
||||
deviceService.delete(id, principal.getName());
|
||||
deviceService.deleteByIdAsOwner(id, principal.getName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ public class SmartPlugController {
|
|||
@DeleteMapping("/{id}")
|
||||
public void deleteById(@PathVariable("id") long id, final Principal principal)
|
||||
throws NotFoundException {
|
||||
deviceService.delete(id, principal.getName());
|
||||
deviceService.deleteByIdAsOwner(id, principal.getName());
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/state")
|
||||
|
|
|
@ -37,6 +37,11 @@ public class SwitchController extends InputDeviceConnectionController<Switch, Sw
|
|||
this.switchRepository = inputRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public Switch findById(@PathVariable("id") long id) throws NotFoundException {
|
||||
return switchRepository.findById(id).orElseThrow(NotFoundException::new);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Switch create(@Valid @RequestBody GenericDeviceSaveReguest s, final Principal principal)
|
||||
throws NotFoundException {
|
||||
|
@ -70,12 +75,12 @@ public class SwitchController extends InputDeviceConnectionController<Switch, Sw
|
|||
}
|
||||
|
||||
deviceService.saveAsOwner(s, principal.getName());
|
||||
return deviceService.saveAllAsOwner(s.getOutputs(), principal.getName(), false);
|
||||
return deviceService.saveAllAsOwner(s.getOutputs(), principal.getName());
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public void deleteById(@PathVariable("id") long id, final Principal principal)
|
||||
throws NotFoundException {
|
||||
deviceService.delete(id, principal.getName());
|
||||
deviceService.deleteByIdAsOwner(id, principal.getName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,14 +25,17 @@ public class ThermostatController {
|
|||
|
||||
private Thermostat save(Thermostat newT, ThermostatSaveRequest t, final Principal principal) {
|
||||
newT.setTargetTemperature(t.getTargetTemperature());
|
||||
thermostatService.populateMeasuredTemperature(newT);
|
||||
newT.setId(t.getId());
|
||||
newT.setName(t.getName());
|
||||
newT.setRoomId(t.getRoomId());
|
||||
newT.setMeasuredTemperature(t.getMeasuredTemperature());
|
||||
newT.setUseExternalSensors(t.isUseExternalSensors());
|
||||
newT.setOn(t.isTurnOn());
|
||||
newT.setOn(false);
|
||||
System.out.println(newT);
|
||||
|
||||
thermostatService.populateMeasuredTemperature(newT);
|
||||
newT = thermostatRepository.save(newT);
|
||||
|
||||
newT.setOn(t.isTurnOn());
|
||||
newT = deviceService.saveAsOwner(newT, principal.getName());
|
||||
return newT;
|
||||
}
|
||||
|
@ -58,7 +61,7 @@ public class ThermostatController {
|
|||
@DeleteMapping("/{id}")
|
||||
public void deleteById(@PathVariable("id") long id, final Principal principal)
|
||||
throws NotFoundException {
|
||||
deviceService.delete(id, principal.getName());
|
||||
deviceService.deleteByIdAsOwner(id, principal.getName());
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/state")
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
|
||||
|
||||
import java.util.List;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class GuestsUpdateRequest {
|
||||
@NotNull public List<Long> ids;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
|
||||
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Room;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Icon;
|
||||
import javax.persistence.Lob;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
|
@ -9,7 +9,7 @@ public class RoomSaveRequest {
|
|||
/** Room identifier */
|
||||
private long id;
|
||||
|
||||
@NotNull private Room.Icon icon;
|
||||
@NotNull private Icon icon;
|
||||
|
||||
/**
|
||||
* Image is to be given as byte[]. In order to get an encoded string from it, the
|
||||
|
@ -38,11 +38,11 @@ public class RoomSaveRequest {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public Room.Icon getIcon() {
|
||||
public Icon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(Room.Icon icon) {
|
||||
public void setIcon(Icon icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
|
||||
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Icon;
|
||||
import com.sun.istack.NotNull;
|
||||
import javax.persistence.Column;
|
||||
|
||||
|
@ -11,6 +12,8 @@ public class SceneSaveRequest {
|
|||
/** The user given name of this room (e.g. 'Master bedroom') */
|
||||
@NotNull private String name;
|
||||
|
||||
@NotNull private Icon icon;
|
||||
|
||||
/** Determines whether a guest can access this scene */
|
||||
@Column @NotNull private boolean guestAccessEnabled;
|
||||
|
||||
|
@ -33,4 +36,12 @@ public class SceneSaveRequest {
|
|||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Icon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(Icon icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@ public class ThermostatSaveRequest {
|
|||
|
||||
@NotNull private boolean useExternalSensors;
|
||||
|
||||
@NotNull private BigDecimal measuredTemperature;
|
||||
|
||||
/** State of this thermostat */
|
||||
@NotNull private boolean turnOn;
|
||||
|
||||
|
@ -74,12 +72,4 @@ public class ThermostatSaveRequest {
|
|||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public BigDecimal getMeasuredTemperature() {
|
||||
return measuredTemperature;
|
||||
}
|
||||
|
||||
public void setMeasuredTemperature(BigDecimal measuredTemperature) {
|
||||
this.measuredTemperature = measuredTemperature;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
|
||||
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
|
||||
|
||||
public class UserResponse {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String name;
|
||||
|
||||
private UserResponse() {}
|
||||
|
||||
public static UserResponse fromUser(User u) {
|
||||
final UserResponse us = new UserResponse();
|
||||
us.name = u.getName();
|
||||
us.id = u.getId();
|
||||
us.username = u.getUsername();
|
||||
return us;
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ public class Automation {
|
|||
@GsonExclude
|
||||
private Long userId;
|
||||
|
||||
@OneToMany(mappedBy = "automation", orphanRemoval = true)
|
||||
@OneToMany(mappedBy = "automation", orphanRemoval = true, cascade = CascadeType.REMOVE)
|
||||
private Set<Trigger<?>> triggers = new HashSet<>();
|
||||
|
||||
@OneToMany(mappedBy = "automation", cascade = CascadeType.REMOVE)
|
||||
|
|
|
@ -71,14 +71,18 @@ public abstract class Device {
|
|||
@SocketGsonExclude
|
||||
private Set<State<?>> states = new HashSet<>();
|
||||
|
||||
@Transient @GsonExclude private boolean fromHost = false;
|
||||
@Transient @GsonExclude private Long fromHostId = null;
|
||||
|
||||
@Transient @GsonExclude private boolean fromGuest = false;
|
||||
|
||||
@Transient @GsonExclude private boolean deleted = false;
|
||||
|
||||
public boolean isFromHost() {
|
||||
return fromHost;
|
||||
public Long getFromHostId() {
|
||||
return fromHostId;
|
||||
}
|
||||
|
||||
public void setFromHostId(Long fromHostId) {
|
||||
this.fromHostId = fromHostId;
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
|
@ -97,10 +101,6 @@ public abstract class Device {
|
|||
this.fromGuest = fromGuest;
|
||||
}
|
||||
|
||||
public void setFromHost(boolean fromHost) {
|
||||
this.fromHost = fromHost;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
|
|
@ -25,4 +25,11 @@ public class DimmableState<T extends Dimmable> extends State<T> {
|
|||
public void apply() {
|
||||
getDevice().readStateAndSet(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State<T> copy() {
|
||||
final DimmableState<T> d = new DimmableState<>();
|
||||
d.setIntensity(intensity);
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/** A collection of Semantic UI icons */
|
||||
@SuppressWarnings("unused")
|
||||
public enum Icon {
|
||||
@SerializedName("home")
|
||||
HOME("home"),
|
||||
@SerializedName("coffee")
|
||||
COFFEE("coffee"),
|
||||
@SerializedName("beer")
|
||||
BEER("beer"),
|
||||
@SerializedName("glass martini")
|
||||
GLASS_MARTINI("glass martini"),
|
||||
@SerializedName("film")
|
||||
FILM("film"),
|
||||
@SerializedName("video")
|
||||
VIDEO("video"),
|
||||
@SerializedName("music")
|
||||
MUSIC("music"),
|
||||
@SerializedName("headphones")
|
||||
HEADPHONES("headphones"),
|
||||
@SerializedName("fax")
|
||||
FAX("fax"),
|
||||
@SerializedName("phone")
|
||||
PHONE("phone"),
|
||||
@SerializedName("laptop")
|
||||
LAPTOP("laptop"),
|
||||
@SerializedName("bath")
|
||||
BATH("bath"),
|
||||
@SerializedName("shower")
|
||||
SHOWER("shower"),
|
||||
@SerializedName("bed")
|
||||
BED("bed"),
|
||||
@SerializedName("child")
|
||||
CHILD("child"),
|
||||
@SerializedName("warehouse")
|
||||
WAREHOUSE("warehouse"),
|
||||
@SerializedName("car")
|
||||
CAR("car"),
|
||||
@SerializedName("bicycle")
|
||||
BICYCLE("bicycle"),
|
||||
@SerializedName("motorcycle")
|
||||
MOTORCYCLE("motorcycle"),
|
||||
@SerializedName("archive")
|
||||
ARCHIVE("archive"),
|
||||
@SerializedName("boxes")
|
||||
BOXES("boxes"),
|
||||
@SerializedName("cubes")
|
||||
CUBES("cubes"),
|
||||
@SerializedName("chess")
|
||||
CHESS("chess"),
|
||||
@SerializedName("gamepad")
|
||||
GAMEPAD("gamepad"),
|
||||
@SerializedName("futbol")
|
||||
FUTBOL("futbol"),
|
||||
@SerializedName("table tennis")
|
||||
TABLE_TENNIS("table tennis"),
|
||||
@SerializedName("server")
|
||||
SERVER("server"),
|
||||
@SerializedName("tv")
|
||||
TV("tv"),
|
||||
@SerializedName("heart")
|
||||
HEART("heart"),
|
||||
@SerializedName("camera")
|
||||
CAMERA("camera"),
|
||||
@SerializedName("trophy")
|
||||
TROPHY("trophy"),
|
||||
@SerializedName("wrench")
|
||||
WRENCH("wrench"),
|
||||
@SerializedName("image")
|
||||
IMAGE("image"),
|
||||
@SerializedName("book")
|
||||
BOOK("book"),
|
||||
@SerializedName("university")
|
||||
UNIVERSITY("university"),
|
||||
@SerializedName("medkit")
|
||||
MEDKIT("medkit"),
|
||||
@SerializedName("paw")
|
||||
PAW("paw"),
|
||||
@SerializedName("tree")
|
||||
TREE("tree"),
|
||||
@SerializedName("utensils")
|
||||
UTENSILS("utensils"),
|
||||
@SerializedName("male")
|
||||
MALE("male"),
|
||||
@SerializedName("female")
|
||||
FEMALE("female"),
|
||||
@SerializedName("life ring outline")
|
||||
LIFE_RING_OUTLINE("life ring outline");
|
||||
|
||||
private String iconName;
|
||||
|
||||
Icon(String s) {
|
||||
this.iconName = s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return iconName;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
||||
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
@ -12,106 +11,6 @@ import javax.validation.constraints.NotNull;
|
|||
@Entity
|
||||
public class Room {
|
||||
|
||||
/** A collection of Semantic UI icons */
|
||||
@SuppressWarnings("unused")
|
||||
public enum Icon {
|
||||
@SerializedName("home")
|
||||
HOME("home"),
|
||||
@SerializedName("coffee")
|
||||
COFFEE("coffee"),
|
||||
@SerializedName("beer")
|
||||
BEER("beer"),
|
||||
@SerializedName("glass martini")
|
||||
GLASS_MARTINI("glass martini"),
|
||||
@SerializedName("film")
|
||||
FILM("film"),
|
||||
@SerializedName("video")
|
||||
VIDEO("video"),
|
||||
@SerializedName("music")
|
||||
MUSIC("music"),
|
||||
@SerializedName("headphones")
|
||||
HEADPHONES("headphones"),
|
||||
@SerializedName("fax")
|
||||
FAX("fax"),
|
||||
@SerializedName("phone")
|
||||
PHONE("phone"),
|
||||
@SerializedName("laptop")
|
||||
LAPTOP("laptop"),
|
||||
@SerializedName("bath")
|
||||
BATH("bath"),
|
||||
@SerializedName("shower")
|
||||
SHOWER("shower"),
|
||||
@SerializedName("bed")
|
||||
BED("bed"),
|
||||
@SerializedName("child")
|
||||
CHILD("child"),
|
||||
@SerializedName("warehouse")
|
||||
WAREHOUSE("warehouse"),
|
||||
@SerializedName("car")
|
||||
CAR("car"),
|
||||
@SerializedName("bicycle")
|
||||
BICYCLE("bicycle"),
|
||||
@SerializedName("motorcycle")
|
||||
MOTORCYCLE("motorcycle"),
|
||||
@SerializedName("archive")
|
||||
ARCHIVE("archive"),
|
||||
@SerializedName("boxes")
|
||||
BOXES("boxes"),
|
||||
@SerializedName("cubes")
|
||||
CUBES("cubes"),
|
||||
@SerializedName("chess")
|
||||
CHESS("chess"),
|
||||
@SerializedName("gamepad")
|
||||
GAMEPAD("gamepad"),
|
||||
@SerializedName("futbol")
|
||||
FUTBOL("futbol"),
|
||||
@SerializedName("table tennis")
|
||||
TABLE_TENNIS("table tennis"),
|
||||
@SerializedName("server")
|
||||
SERVER("server"),
|
||||
@SerializedName("tv")
|
||||
TV("tv"),
|
||||
@SerializedName("heart")
|
||||
HEART("heart"),
|
||||
@SerializedName("camera")
|
||||
CAMERA("camera"),
|
||||
@SerializedName("trophy")
|
||||
TROPHY("trophy"),
|
||||
@SerializedName("wrench")
|
||||
WRENCH("wrench"),
|
||||
@SerializedName("image")
|
||||
IMAGE("image"),
|
||||
@SerializedName("book")
|
||||
BOOK("book"),
|
||||
@SerializedName("university")
|
||||
UNIVERSITY("university"),
|
||||
@SerializedName("medkit")
|
||||
MEDKIT("medkit"),
|
||||
@SerializedName("paw")
|
||||
PAW("paw"),
|
||||
@SerializedName("tree")
|
||||
TREE("tree"),
|
||||
@SerializedName("utensils")
|
||||
UTENSILS("utensils"),
|
||||
@SerializedName("male")
|
||||
MALE("male"),
|
||||
@SerializedName("female")
|
||||
FEMALE("female"),
|
||||
@SerializedName("life ring outline")
|
||||
LIFE_RING_OUTLINE("life ring outline");
|
||||
|
||||
private String iconName;
|
||||
|
||||
Icon(String s) {
|
||||
this.iconName = s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return iconName;
|
||||
}
|
||||
}
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
@Column(name = "id", updatable = false, nullable = false, unique = true)
|
||||
|
|
|
@ -39,9 +39,21 @@ public class Scene {
|
|||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(nullable = false)
|
||||
@NotNull
|
||||
private Icon icon;
|
||||
|
||||
/** Determines whether a guest can access this scene */
|
||||
@Column private boolean guestAccessEnabled;
|
||||
|
||||
public Icon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(Icon icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public boolean isGuestAccessEnabled() {
|
||||
return guestAccessEnabled;
|
||||
}
|
||||
|
|
|
@ -19,4 +19,9 @@ public interface SceneRepository extends CrudRepository<Scene, Long> {
|
|||
|
||||
@Query("SELECT s FROM Scene s JOIN s.user u WHERE u.username = ?1")
|
||||
List<Scene> findByUsername(String username);
|
||||
|
||||
@Query("SELECT s FROM Scene s JOIN s.user u WHERE u.id = ?1 AND s.guestAccessEnabled = true")
|
||||
List<Scene> findByHostId(Long hostId);
|
||||
|
||||
Optional<Scene> findByIdAndUserId(Long id, Long userId);
|
||||
}
|
||||
|
|
|
@ -45,6 +45,16 @@ public abstract class State<D extends OutputDevice> {
|
|||
/** Sets the state of the connected device to the state represented by this object. */
|
||||
public abstract void apply();
|
||||
|
||||
/** Creates a perfect copy of this state, except for the id field and the sceneId/scene */
|
||||
protected abstract State<D> copy();
|
||||
|
||||
public State<D> copyToSceneId(Long sceneId) {
|
||||
final State<D> s = copy();
|
||||
s.setDeviceId(this.deviceId);
|
||||
s.setSceneId(sceneId);
|
||||
return s;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,13 @@ import javax.persistence.*;
|
|||
@Entity
|
||||
public class Switch extends InputDevice implements BooleanTriggerable {
|
||||
|
||||
@ManyToMany(cascade = CascadeType.DETACH)
|
||||
@ManyToMany(
|
||||
cascade = {
|
||||
CascadeType.DETACH,
|
||||
CascadeType.MERGE,
|
||||
CascadeType.REFRESH,
|
||||
CascadeType.PERSIST
|
||||
})
|
||||
@GsonExclude
|
||||
@SocketGsonExclude
|
||||
@JoinTable(
|
||||
|
|
|
@ -14,7 +14,14 @@ public abstract class Switchable extends OutputDevice {
|
|||
public static final Connector<Switch, Switchable> SWITCH_SWITCHABLE_CONNECTOR =
|
||||
Connector.basic(Switch::getOutputs, Switchable::getSwitches);
|
||||
|
||||
@ManyToMany(mappedBy = "switchables", cascade = CascadeType.DETACH)
|
||||
@ManyToMany(
|
||||
mappedBy = "switchables",
|
||||
cascade = {
|
||||
CascadeType.DETACH,
|
||||
CascadeType.MERGE,
|
||||
CascadeType.REFRESH,
|
||||
CascadeType.PERSIST
|
||||
})
|
||||
@GsonExclude
|
||||
@SocketGsonExclude
|
||||
private Set<Switch> inputs = new HashSet<>();
|
||||
|
|
|
@ -22,4 +22,11 @@ public class SwitchableState<T extends Switchable> extends State<T> {
|
|||
public void apply() {
|
||||
getDevice().readStateAndSet(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State<T> copy() {
|
||||
final SwitchableState<T> d = new SwitchableState<>();
|
||||
d.setOn(on);
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,11 @@ import javax.persistence.Column;
|
|||
import javax.persistence.Entity;
|
||||
import javax.persistence.Transient;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** A thermostat capable of controlling cooling and heating. */
|
||||
@Entity
|
||||
@Component
|
||||
public class Thermostat extends Switchable implements BooleanTriggerable {
|
||||
|
||||
@Override
|
||||
|
@ -22,32 +24,29 @@ public class Thermostat extends Switchable implements BooleanTriggerable {
|
|||
computeState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the new thermostat state, for when the thermostat is on;
|
||||
*
|
||||
* @return true if the state changed, false if not;
|
||||
*/
|
||||
public boolean computeState() {
|
||||
/** Computes the new thermostat state, for when the thermostat is on */
|
||||
public void computeState() {
|
||||
if (mode == Thermostat.Mode.OFF) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
BigDecimal measured = this.getMeasuredTemperature();
|
||||
BigDecimal target = this.getTargetTemperature();
|
||||
|
||||
if (measured == null) {
|
||||
this.setMode(Thermostat.Mode.IDLE);
|
||||
return;
|
||||
}
|
||||
|
||||
BigDecimal delta = target.subtract(measured);
|
||||
|
||||
if (delta.abs().doubleValue() < 0.25) {
|
||||
if (this.getMode() == Thermostat.Mode.IDLE) return false;
|
||||
this.setMode(Thermostat.Mode.IDLE);
|
||||
} else if (delta.signum() > 0) {
|
||||
if (this.getMode() == Thermostat.Mode.HEATING) return false;
|
||||
this.setMode(Thermostat.Mode.HEATING);
|
||||
} else {
|
||||
if (this.getMode() == Thermostat.Mode.COOLING) return false;
|
||||
this.setMode(Thermostat.Mode.COOLING);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,6 +86,18 @@ public class Thermostat extends Switchable implements BooleanTriggerable {
|
|||
this.mode = Mode.OFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("Thermostat{");
|
||||
sb.append("targetTemperature=").append(targetTemperature);
|
||||
sb.append(", internalSensorTemperature=").append(internalSensorTemperature);
|
||||
sb.append(", mode=").append(mode);
|
||||
sb.append(", measuredTemperature=").append(measuredTemperature);
|
||||
sb.append(", useExternalSensors=").append(useExternalSensors);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void setMode(Mode state) {
|
||||
this.mode = state;
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ public class UpdateTasks {
|
|||
c.forEach(
|
||||
s ->
|
||||
sensorSocketEndpoint.queueDeviceUpdate(
|
||||
s, sensorRepository.findUser(s.getId())));
|
||||
s, sensorRepository.findUser(s.getId()), false, null, false));
|
||||
}
|
||||
|
||||
/** Sends device updates through sensor socket in batch every one second */
|
||||
|
|
|
@ -9,6 +9,8 @@ import java.util.Collection;
|
|||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
@ -37,7 +39,7 @@ public class DeviceService {
|
|||
}
|
||||
}
|
||||
|
||||
private void triggerTriggers(Device device) {
|
||||
private void triggerTriggers(Device device, final String username) {
|
||||
|
||||
final long deviceId = device.getId();
|
||||
|
||||
|
@ -57,7 +59,7 @@ public class DeviceService {
|
|||
sceneRepository
|
||||
.findById(t.getSceneId())
|
||||
.orElseThrow(IllegalStateException::new))
|
||||
.forEach(sceneService::apply);
|
||||
.forEach((s) -> sceneService.apply(s, username, true));
|
||||
}
|
||||
|
||||
public List<Device> findAll(Long hostId, String username) throws NotFoundException {
|
||||
|
@ -68,6 +70,7 @@ public class DeviceService {
|
|||
throws NotFoundException {
|
||||
try {
|
||||
Iterable<Device> devices;
|
||||
User host = null;
|
||||
if (hostId == null) {
|
||||
if (roomId != null) {
|
||||
roomRepository
|
||||
|
@ -79,8 +82,7 @@ public class DeviceService {
|
|||
}
|
||||
} else {
|
||||
final User guest = userRepository.findByUsername(username);
|
||||
final User host =
|
||||
userRepository.findById(hostId).orElseThrow(NotFoundException::new);
|
||||
host = userRepository.findById(hostId).orElseThrow(NotFoundException::new);
|
||||
|
||||
if (!guest.getHosts().contains(host)) {
|
||||
throw new NotFoundException();
|
||||
|
@ -97,17 +99,27 @@ public class DeviceService {
|
|||
}
|
||||
}
|
||||
|
||||
populateComputedFields(devices);
|
||||
|
||||
if (host != null && !host.isCameraEnabled()) {
|
||||
return StreamSupport.stream(devices.spliterator(), true)
|
||||
.filter(d -> !(d instanceof SecurityCamera))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
return toList(devices);
|
||||
}
|
||||
} catch (NotFoundException e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void populateComputedFields(Iterable<Device> devices) {
|
||||
for (Device d : devices) {
|
||||
if (d instanceof Thermostat) {
|
||||
thermostatService.populateMeasuredTemperature((Thermostat) d);
|
||||
}
|
||||
}
|
||||
|
||||
return toList(devices);
|
||||
} catch (NotFoundException e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends Device> T saveAsGuest(T device, String guestUsername, Long hostId)
|
||||
|
@ -116,79 +128,119 @@ public class DeviceService {
|
|||
final User host = userRepository.findById(hostId).orElseThrow(NotFoundException::new);
|
||||
if (!host.getGuests().contains(currentUser)) throw new NotFoundException();
|
||||
renameIfDuplicate(device, host.getUsername());
|
||||
|
||||
device = deviceRepository.save(device);
|
||||
propagateUpdateAsGuest(device, host, currentUser);
|
||||
return device;
|
||||
}
|
||||
|
||||
private void propagateUpdateAsGuest(Device device, User host, User guest) {
|
||||
final Set<User> guests = Set.copyOf(host.getGuests());
|
||||
|
||||
// We're telling the host that a guest has modified a device. Therefore, fromGuest becomes
|
||||
// true.
|
||||
device.setFromHost(false);
|
||||
device.setFromGuest(true);
|
||||
// broadcast device update to host
|
||||
endpoint.queueDeviceUpdate(device, host);
|
||||
endpoint.queueDeviceUpdate(device, host, true, null, false);
|
||||
|
||||
// We're telling all guests that a higher entity has issued a device update. Therefore,
|
||||
// fromHost becomes true.
|
||||
device.setFromHost(true);
|
||||
device.setFromGuest(false);
|
||||
for (final User guest : guests) {
|
||||
if (guest.equals(currentUser)) {
|
||||
for (final User aGuest : guests) {
|
||||
if (aGuest.equals(guest)) {
|
||||
continue;
|
||||
}
|
||||
// enqueue all device updates for all other guests
|
||||
endpoint.queueDeviceUpdate(device, guest);
|
||||
endpoint.queueDeviceUpdate(device, aGuest, false, host.getId(), false);
|
||||
}
|
||||
}
|
||||
|
||||
return device;
|
||||
List<Device> saveAllAsGuestSceneApplication(
|
||||
List<Device> devices, String guestUsername, Long hostId) {
|
||||
final User guest = userRepository.findByUsername(guestUsername);
|
||||
final User host = userRepository.findById(hostId).orElseThrow(IllegalStateException::new);
|
||||
deviceRepository.saveAll(devices);
|
||||
devices.forEach(d -> this.propagateUpdateAsGuest(d, host, guest));
|
||||
return devices;
|
||||
}
|
||||
|
||||
private void propagateUpdateAsOwner(Device device, String username) {
|
||||
/**
|
||||
* Propagates the update through the socket assuming that the user that modified the device is
|
||||
* the owner of that device
|
||||
*
|
||||
* @param device the updated device
|
||||
* @param username the username of the owner of that device
|
||||
* @param causedByTrigger if true, send the update to the owner as well
|
||||
*/
|
||||
private void propagateUpdateAsOwner(Device device, String username, boolean causedByTrigger) {
|
||||
final User user = userRepository.findByUsername(username);
|
||||
final Set<User> guests = user.getGuests();
|
||||
// make sure we're broadcasting from host
|
||||
device.setFromHost(true);
|
||||
device.setFromGuest(false);
|
||||
for (final User guest : guests) {
|
||||
// broadcast to endpoint the object device, with receiving user set to guest
|
||||
endpoint.queueDeviceUpdate(device, guest);
|
||||
endpoint.queueDeviceUpdate(device, guest, false, user.getId(), false);
|
||||
}
|
||||
|
||||
if (causedByTrigger) {
|
||||
endpoint.queueDeviceUpdate(device, user, false, user.getId(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all the devices given in devices assuming that the owner updated them in one way or
|
||||
* another. Takes care of the appropriate websocket updates and trigger checking as well. No
|
||||
* checking is done to verify that the user whose username is given is in fact the owner of
|
||||
* these devices
|
||||
*
|
||||
* @param devices the list of devices to save
|
||||
* @param username the username of the owner of these devices
|
||||
* @param fromScene true if the update comes from the a scene application side effect. Disables
|
||||
* trigger checking to avoid recursive invocations of automations
|
||||
* @param fromTrigger true if the update comes from a scene application executed by an
|
||||
* automation. Propagates updated through socket to owner as well. No effect if fromScene is
|
||||
* false.
|
||||
* @param <T> the type of device contained in the list
|
||||
* @return the updated list of devices, ready to be fed to GSON
|
||||
*/
|
||||
public <T extends Device> List<T> saveAllAsOwner(
|
||||
Iterable<T> devices, String username, boolean fromScene) {
|
||||
Iterable<T> devices, String username, boolean fromScene, boolean fromTrigger) {
|
||||
devices.forEach(d -> renameIfDuplicate(d, username));
|
||||
devices = deviceRepository.saveAll(devices);
|
||||
devices.forEach((d) -> propagateUpdateAsOwner(d, username));
|
||||
devices.forEach((d) -> propagateUpdateAsOwner(d, username, fromScene && fromTrigger));
|
||||
|
||||
if (!fromScene) {
|
||||
devices.forEach(this::triggerTriggers);
|
||||
devices.forEach((d) -> triggerTriggers(d, username));
|
||||
}
|
||||
|
||||
return toList(devices);
|
||||
}
|
||||
|
||||
public <T extends Device> T saveAsOwner(T device, String username, boolean fromScene) {
|
||||
public <T extends Device> List<T> saveAllAsOwner(Iterable<T> devices, String username) {
|
||||
return saveAllAsOwner(devices, username, false, false);
|
||||
}
|
||||
|
||||
public <T extends Device> T saveAsOwner(T device, String username) {
|
||||
renameIfDuplicate(device, username);
|
||||
device = deviceRepository.save(device);
|
||||
propagateUpdateAsOwner(device, username);
|
||||
propagateUpdateAsOwner(device, username, false);
|
||||
|
||||
if (!fromScene) {
|
||||
triggerTriggers(device);
|
||||
}
|
||||
triggerTriggers(device, username);
|
||||
|
||||
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 {
|
||||
Device device =
|
||||
public void deleteByIdAsOwner(Long id, String username) throws NotFoundException {
|
||||
Device d =
|
||||
deviceRepository
|
||||
.findByIdAndUsername(id, username)
|
||||
.orElseThrow(NotFoundException::new);
|
||||
deviceRepository.delete(device);
|
||||
|
||||
propagateUpdateAsOwner(device, username);
|
||||
final User user = userRepository.findByUsername(username);
|
||||
final Set<User> guests = user.getGuests();
|
||||
// make sure we're broadcasting from host
|
||||
for (final User guest : guests) {
|
||||
// broadcast to endpoint the object device, with receiving user set to guest
|
||||
endpoint.queueDeviceUpdate(d, guest, false, user.getId(), true);
|
||||
}
|
||||
|
||||
deviceRepository.delete(d);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ public class MotionSensorService {
|
|||
@Autowired private MotionSensorRepository motionSensorRepository;
|
||||
|
||||
/**
|
||||
* Updates detection status of given motion sensor and propagates update throgh socket
|
||||
* Updates detection status of given motion sensor and propagates update through socket
|
||||
*
|
||||
* @param sensor the motion sensor to update
|
||||
* @param detected the new detection status
|
||||
|
@ -26,7 +26,7 @@ public class MotionSensorService {
|
|||
final MotionSensor toReturn = deviceService.saveAsOwner(sensor, username);
|
||||
|
||||
sensorSocketEndpoint.queueDeviceUpdate(
|
||||
sensor, motionSensorRepository.findUser(sensor.getId()));
|
||||
sensor, motionSensorRepository.findUser(sensor.getId()), false, null, false);
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
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 ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -13,16 +10,38 @@ import org.springframework.stereotype.Component;
|
|||
public class SceneService {
|
||||
|
||||
@Autowired private DeviceRepository<Device> deviceRepository;
|
||||
@Autowired private DeviceService deviceService;
|
||||
@Autowired private StateRepository<State<?>> stateRepository;
|
||||
|
||||
public List<Device> apply(Scene newScene) {
|
||||
private List<Device> copyStatesToDevices(Scene fromScene) {
|
||||
final List<Device> updated = new ArrayList<>();
|
||||
|
||||
for (final State<?> s : newScene.getStates()) {
|
||||
for (final State<?> s : fromScene.getStates()) {
|
||||
s.apply();
|
||||
updated.add(s.getDevice());
|
||||
}
|
||||
deviceRepository.saveAll(updated);
|
||||
|
||||
deviceService.populateComputedFields(updated);
|
||||
return updated;
|
||||
}
|
||||
|
||||
public List<Device> apply(Scene newScene, String username, boolean fromTrigger) {
|
||||
List<Device> updated = copyStatesToDevices(newScene);
|
||||
deviceService.saveAllAsOwner(updated, username, true, fromTrigger);
|
||||
return updated;
|
||||
}
|
||||
|
||||
public List<Device> applyAsGuest(Scene newScene, String username, Long hostId) {
|
||||
List<Device> updated = copyStatesToDevices(newScene);
|
||||
deviceService.saveAllAsGuestSceneApplication(updated, username, hostId);
|
||||
return updated;
|
||||
}
|
||||
|
||||
public List<State<?>> copyStates(Scene to, Scene from) {
|
||||
final ArrayList<State<?>> states = new ArrayList<>();
|
||||
for (final State<?> s : from.getStates()) {
|
||||
states.add(stateRepository.save(s.copyToSceneId(to.getId())));
|
||||
}
|
||||
return states;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,8 @@ public class SensorService {
|
|||
sensor =
|
||||
deviceService.saveAsOwner(
|
||||
sensor, sensorRepository.findUser(sensor.getId()).getUsername());
|
||||
endpoint.queueDeviceUpdate(sensor, sensorRepository.findUser(sensor.getId()));
|
||||
endpoint.queueDeviceUpdate(
|
||||
sensor, sensorRepository.findUser(sensor.getId()), false, null, false);
|
||||
return sensor;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,18 +45,16 @@ public class ThermostatService {
|
|||
return Utils.toList(all);
|
||||
}
|
||||
|
||||
public boolean computeState(Thermostat t) {
|
||||
public void computeState(Thermostat t) {
|
||||
populateMeasuredTemperature(t);
|
||||
return t.computeState();
|
||||
t.computeState();
|
||||
}
|
||||
|
||||
private void updateState(Thermostat t) {
|
||||
boolean shouldUpdate = this.computeState(t);
|
||||
this.computeState(t);
|
||||
|
||||
if (shouldUpdate) {
|
||||
deviceService.saveAsOwner(t, thermostatRepository.findUser(t.getId()).getUsername());
|
||||
endpoint.queueDeviceUpdate(t, thermostatRepository.findUser(t.getId()));
|
||||
}
|
||||
endpoint.queueDeviceUpdate(t, thermostatRepository.findUser(t.getId()), false, null, false);
|
||||
}
|
||||
|
||||
public void updateStates() {
|
||||
|
@ -72,7 +70,6 @@ public class ThermostatService {
|
|||
populateMeasuredTemperature(u);
|
||||
t = Optional.of(u);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtils;
|
|||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Device;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
|
@ -21,6 +22,8 @@ public class SensorSocketEndpoint extends Endpoint {
|
|||
|
||||
private Gson gson = GsonConfig.socketGson();
|
||||
|
||||
@Autowired private DeviceService deviceService;
|
||||
|
||||
private UserRepository userRepository;
|
||||
|
||||
private JWTTokenUtils jwtTokenUtils;
|
||||
|
@ -28,7 +31,10 @@ public class SensorSocketEndpoint extends Endpoint {
|
|||
private Multimap<User, Session> authorizedClients =
|
||||
Multimaps.synchronizedMultimap(HashMultimap.create());
|
||||
|
||||
private final Map<User, Map<Long, Device>> messages = new HashMap<>();
|
||||
// messages are now stored as strings as a "hack" to capture and clone the state of the device,
|
||||
// since
|
||||
// fromHost and fromGuest are just mutable properties and hibernate caches the object.
|
||||
private final Map<User, Map<Long, String>> messages = new HashMap<>();
|
||||
|
||||
@Autowired
|
||||
public SensorSocketEndpoint(UserRepository userRepository, JWTTokenUtils jwtTokenUtils) {
|
||||
|
@ -41,18 +47,33 @@ public class SensorSocketEndpoint extends Endpoint {
|
|||
*
|
||||
* @param device the device update to be sent
|
||||
* @param u the user the device belongs
|
||||
* @param fromGuest value for device.fromGuest. This will be put in the device passed.
|
||||
* @param fromHostId value for device.fromHostId. This will be put in the device passed.
|
||||
* @param deleted value for device.deleted. This will be put in the device passed.
|
||||
*/
|
||||
public void queueDeviceUpdate(Device device, User u) {
|
||||
public void queueDeviceUpdate(
|
||||
Device device, User u, boolean fromGuest, Long fromHostId, boolean deleted) {
|
||||
synchronized (messages) {
|
||||
device.setFromGuest(fromGuest);
|
||||
device.setFromHostId(fromHostId);
|
||||
device.setDeleted(deleted);
|
||||
|
||||
// sort of an hack: force the population of thermostat measureTemperature and other
|
||||
// possible
|
||||
// computed fields in the future. This should already be done by the callers of this
|
||||
// method but for
|
||||
// whatever reason they don't do it.
|
||||
deviceService.populateComputedFields(List.of(device));
|
||||
|
||||
messages.putIfAbsent(u, new HashMap<>());
|
||||
messages.get(u).put(device.getId(), device);
|
||||
messages.get(u).put(device.getId(), gson.toJson(device));
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends all device updates queued to be sent in a unique WebSocket message */
|
||||
public void flushDeviceUpdates() {
|
||||
synchronized (messages) {
|
||||
for (Map.Entry<User, Map<Long, Device>> batchForUser : messages.entrySet()) {
|
||||
for (Map.Entry<User, Map<Long, String>> batchForUser : messages.entrySet()) {
|
||||
broadcast(batchForUser.getKey(), batchForUser.getValue().values());
|
||||
batchForUser.getValue().clear();
|
||||
}
|
||||
|
@ -66,13 +87,13 @@ public class SensorSocketEndpoint extends Endpoint {
|
|||
* @param messages the message batch to send
|
||||
* @param u the user to which to send the message
|
||||
*/
|
||||
private void broadcast(User u, Collection<?> messages) {
|
||||
private void broadcast(User u, Collection<String> messages) {
|
||||
if (messages.isEmpty()) return;
|
||||
final HashSet<Session> sessions = new HashSet<>(authorizedClients.get(u));
|
||||
for (Session s : sessions) {
|
||||
try {
|
||||
if (s.isOpen()) {
|
||||
s.getBasicRemote().sendText(gson.toJson(messages));
|
||||
s.getBasicRemote().sendText("[" + String.join(",", messages) + "]");
|
||||
} else {
|
||||
authorizedClients.remove(u, s);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue