diff --git a/socket_test.html b/socket_test.html
index 90feba2..687388b 100644
--- a/socket_test.html
+++ b/socket_test.html
@@ -1,3 +1,5 @@
+
+
@@ -25,6 +27,8 @@ connection.onmessage = function(evt) {
"";
} else if (data.authenticated === false) {
malusa.innerHTML = "Authentication error
";
+ } else {
+ malusa.innerHTML += "" + JSON.stringify(JSON.parse(evt.data), null, 2) + "
";
}
};
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplication.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplication.java
index 242f03f..57f7b42 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplication.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplication.java
@@ -3,8 +3,10 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
+@EnableScheduling
@EnableJpaRepositories("ch.usi.inf.sa4.sanmarinoes.smarthut.models")
public class SmarthutApplication {
public static void main(String[] args) {
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java
index e12d9f0..69c4fc9 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java
@@ -20,7 +20,7 @@ public class GsonConfig {
return converter;
}
- private Gson gson() {
+ public static Gson gson() {
final GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter());
builder.addSerializationExclusionStrategy(new AnnotationExclusionStrategy());
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java
index 4ba03b1..59c0343 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java
@@ -6,6 +6,8 @@ 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.models.MotionSensor;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensorRepository;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
+import java.security.Principal;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
@@ -19,6 +21,8 @@ public class MotionSensorController {
@Autowired private MotionSensorRepository motionSensorService;
+ @Autowired private SensorSocketEndpoint sensorSocketEndpoint;
+
@GetMapping
public List findAll() {
return toList(motionSensorService.findAll());
@@ -38,6 +42,36 @@ public class MotionSensorController {
return motionSensorService.save(newMS);
}
+ /**
+ * 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.broadcast(sensor, motionSensorService.findUser(sensor.getId()));
+
+ return toReturn;
+ }
+
+ @PutMapping("/{id}/detect")
+ public MotionSensor updateDetection(
+ @PathVariable("id") Long sensorId,
+ @RequestParam("detected") boolean detected,
+ final Principal principal)
+ throws NotFoundException {
+
+ return updateDetectionFromMotionSensor(
+ motionSensorService
+ .findByIdAndUsername(sensorId, principal.getName())
+ .orElseThrow(NotFoundException::new),
+ detected);
+ }
+
@DeleteMapping("/{id}")
public void delete(@PathVariable("id") long id) {
motionSensorService.deleteById(id);
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SensorController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SensorController.java
index 3412e0c..ee1be81 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SensorController.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SensorController.java
@@ -5,6 +5,9 @@ import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
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.models.*;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
+import java.math.BigDecimal;
+import java.security.Principal;
import java.util.*;
import java.util.List;
import javax.validation.Valid;
@@ -19,6 +22,8 @@ public class SensorController {
@Autowired private SensorRepository sensorRepository;
+ @Autowired private SensorSocketEndpoint sensorSocketEndpoint;
+
@GetMapping
public List findAll() {
return toList(sensorRepository.findAll());
@@ -35,10 +40,40 @@ public class SensorController {
newSensor.setSensor(s.getSensor());
newSensor.setName(s.getName());
newSensor.setRoomId(s.getRoomId());
+ newSensor.setValue(s.getValue());
return sensorRepository.save(newSensor);
}
+ /**
+ * Updates the sensor with new measurement and propagates update through websocket
+ *
+ * @param sensor the sensor to update
+ * @param value the new measurement
+ * @return the updated sensor
+ */
+ public Sensor updateValueFromSensor(Sensor sensor, BigDecimal value) {
+ sensor.setValue(value);
+ final Sensor toReturn = sensorRepository.save(sensor);
+
+ sensorSocketEndpoint.broadcast(sensor, sensorRepository.findUser(sensor.getId()));
+
+ return toReturn;
+ }
+
+ @PutMapping("/{id}/value")
+ public Sensor updateValue(
+ @PathVariable("id") Long sensorId,
+ @RequestParam("value") BigDecimal value,
+ final Principal principal)
+ throws NotFoundException {
+ return updateValueFromSensor(
+ sensorRepository
+ .findByIdAndUsername(sensorId, principal.getName())
+ .orElseThrow(NotFoundException::new),
+ value);
+ }
+
@DeleteMapping("/{id}")
public void deleteById(@PathVariable("id") long id) {
sensorRepository.deleteById(id);
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SensorSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SensorSaveRequest.java
index e9a5c68..62b0b5e 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SensorSaveRequest.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SensorSaveRequest.java
@@ -2,6 +2,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Sensor;
import com.google.gson.annotations.SerializedName;
+import java.math.BigDecimal;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.validation.constraints.NotNull;
@@ -28,6 +29,8 @@ public class SensorSaveRequest {
@Enumerated(value = EnumType.STRING)
private Sensor.SensorType sensor;
+ @NotNull private BigDecimal value;
+
/**
* The room this device belongs in, as a foreign key id. To use when updating and inserting from
* a REST call.
@@ -60,4 +63,12 @@ public class SensorSaveRequest {
public void setSensor(Sensor.SensorType sensor) {
this.sensor = sensor;
}
+
+ public BigDecimal getValue() {
+ return value;
+ }
+
+ public void setValue(BigDecimal value) {
+ this.value = value;
+ }
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java
index e8a621b..c564914 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java
@@ -1,5 +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 javax.persistence.*;
@@ -26,6 +27,11 @@ public abstract class Device {
@ApiModelProperty(hidden = true)
private long id;
+ @ManyToOne
+ @JoinColumn(name = "room_id", updatable = false, insertable = false)
+ @GsonExclude
+ private Room room;
+
/**
* The room this device belongs in, as a foreign key id. To use when updating and inserting from
* a REST call.
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DeviceRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DeviceRepository.java
index fdae66e..b90639b 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DeviceRepository.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DeviceRepository.java
@@ -1,6 +1,8 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.util.List;
+import java.util.Optional;
+import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
@@ -10,4 +12,23 @@ import org.springframework.data.repository.query.Param;
*/
public interface DeviceRepository extends CrudRepository {
List findByRoomId(@Param("roomId") long roomId);
+
+ /**
+ * Finds devices by their id and a username
+ *
+ * @param id the device id
+ * @param username a User's username
+ * @return an optional device, empty if none found
+ */
+ @Query("SELECT d FROM Device d JOIN d.room r JOIN r.user u WHERE d.id = ?1 AND u.username = ?2")
+ Optional findByIdAndUsername(Long id, String username);
+
+ /**
+ * Find the user associated with a device through a room
+ *
+ * @param deviceId the device id
+ * @return a user object
+ */
+ @Query("SELECT u FROM Device d JOIN d.room r JOIN r.user u WHERE d.id = ?1")
+ User findUser(Long deviceId);
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java
index e54b462..68c3fc0 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java
@@ -1,5 +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 javax.persistence.*;
@@ -128,6 +129,11 @@ public class Room {
@Column(name = "image", columnDefinition = "TEXT")
private String image;
+ @ManyToOne
+ @JoinColumn(name = "user_id", updatable = false, insertable = false)
+ @GsonExclude
+ private User user;
+
/**
* User that owns the house this room is in as a foreign key id. To use when updating and
* inserting from a REST call.
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java
index b1d98a2..525ceb3 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java
@@ -1,6 +1,8 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import com.google.gson.annotations.SerializedName;
+import java.math.BigDecimal;
+import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
@@ -11,6 +13,12 @@ import javax.validation.constraints.NotNull;
@Entity
public class Sensor extends InputDevice {
+ public static final Map TYPICAL_VALUES =
+ Map.of(
+ SensorType.TEMPERATURE, new BigDecimal(17.0),
+ SensorType.HUMIDITY, new BigDecimal(40.0),
+ SensorType.LIGHT, new BigDecimal(1000));
+
/** Type of sensor, i.e. of the thing the sensor measures. */
public enum SensorType {
/** A sensor that measures temperature in degrees celsius */
@@ -27,8 +35,8 @@ public class Sensor extends InputDevice {
}
/** The value of this sensor according to its sensor type */
- @Column(nullable = false)
- private int value;
+ @Column(nullable = false, length = 10, precision = 1)
+ private BigDecimal value;
/** The type of this sensor */
@Column(nullable = false)
@@ -42,19 +50,22 @@ public class Sensor extends InputDevice {
public void setSensor(SensorType sensor) {
this.sensor = sensor;
-
- // TODO: setup hook for sockets live update
}
- public int getValue() {
+ public BigDecimal getValue() {
return this.value;
}
- public void setValue(int newValue) {
+ public void setValue(BigDecimal newValue) {
this.value = newValue;
}
public Sensor() {
super("sensor");
}
+
+ @Override
+ public String toString() {
+ return "Sensor{" + "value=" + value + ", sensor=" + sensor + '}';
+ }
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java
index f1b88ca..dc6766d 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java
@@ -1,6 +1,7 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import io.swagger.annotations.ApiModelProperty;
+import java.util.Objects;
import javax.persistence.*;
/** A user of the Smarthut application */
@@ -105,4 +106,22 @@ public class User {
+ isEnabled
+ '}';
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ User user = (User) o;
+ return id.equals(user.id)
+ && name.equals(user.name)
+ && username.equals(user.username)
+ && password.equals(user.password)
+ && email.equals(user.email)
+ && isEnabled.equals(user.isEnabled);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, name, username, password, email, isEnabled);
+ }
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/SensorUpdateTasks.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/SensorUpdateTasks.java
new file mode 100644
index 0000000..b1614aa
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/SensorUpdateTasks.java
@@ -0,0 +1,62 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.scheduled;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.controller.MotionSensorController;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.controller.SensorController;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensorRepository;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Sensor;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.SensorRepository;
+import java.math.BigDecimal;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.StreamSupport;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+/** Generates fake sensor (and motion sensor) updates as required by milestone one */
+@Component
+public class SensorUpdateTasks {
+
+ @Autowired private SensorRepository sensorRepository;
+
+ @Autowired private MotionSensorRepository motionSensorRepository;
+
+ @Autowired private SensorController sensorController;
+
+ @Autowired private MotionSensorController motionSensorController;
+
+ /** Generates fake sensor updates every two seconds with a +/- 1.25% error */
+ @Scheduled(fixedRate = 2000)
+ public void sensorFakeUpdate() {
+ StreamSupport.stream(sensorRepository.findAll().spliterator(), true)
+ .forEach(
+ sensor ->
+ sensorController.updateValueFromSensor(
+ sensor,
+ Sensor.TYPICAL_VALUES
+ .get(sensor.getSensor())
+ .multiply(
+ new BigDecimal(
+ 0.9875 + Math.random() / 40))));
+ }
+
+ /**
+ * Generate fake motion detections in all motion detectors every 20 seconds for 2 seconds at
+ * most
+ */
+ @Scheduled(fixedDelay = 20000)
+ public void motionSensorFakeUpdate() {
+ StreamSupport.stream(motionSensorRepository.findAll().spliterator(), true)
+ .forEach(
+ sensor -> {
+ motionSensorController.updateDetectionFromMotionSensor(sensor, true);
+ CompletableFuture.delayedExecutor(
+ (long) (Math.random() * 2000), TimeUnit.MILLISECONDS)
+ .execute(
+ () ->
+ motionSensorController
+ .updateDetectionFromMotionSensor(
+ sensor, false));
+ });
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/AuthenticationMessageListener.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/AuthenticationMessageListener.java
index e0b9249..89415aa 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/AuthenticationMessageListener.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/AuthenticationMessageListener.java
@@ -1,5 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.socket;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonConfig;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtils;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
@@ -18,7 +19,7 @@ import org.springframework.stereotype.Component;
@Component
public class AuthenticationMessageListener {
- private Gson gson = new Gson();
+ private Gson gson = GsonConfig.gson();
private JWTTokenUtils jwtTokenUtils;
@@ -43,8 +44,6 @@ public class AuthenticationMessageListener {
return new MessageHandler.Whole<>() {
@Override
public void onMessage(final String message) {
- System.out.println(message);
-
if (message == null) {
acknowledge(false);
return;
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java
index d40e874..bc2f90e 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java
@@ -2,6 +2,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.socket;
import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.didThrow;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonConfig;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
@@ -16,7 +17,7 @@ import org.springframework.stereotype.Component;
@Component
public class SensorSocketEndpoint extends Endpoint {
- private Gson gson = new Gson();
+ private Gson gson = GsonConfig.gson();
private AuthenticationMessageListener authenticationMessageListener;
@@ -60,7 +61,7 @@ public class SensorSocketEndpoint extends Endpoint {
final Collection sessions = authorizedClients.get(u);
return sessions.stream()
.parallel()
- .filter(didThrow(s -> s.getAsyncRemote().sendObject(gson.toJson(message))))
+ .filter(didThrow(s -> s.getBasicRemote().sendText(gson.toJson(message))))
.count();
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/utils/Utils.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/utils/Utils.java
index bc8719d..99d363b 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/utils/Utils.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/utils/Utils.java
@@ -1,8 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.utils;
import java.util.List;
-import java.util.concurrent.Future;
-import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@@ -11,14 +9,19 @@ import java.util.stream.StreamSupport;
public final class Utils {
private Utils() {}
+ @FunctionalInterface
+ public interface ConsumerWithException {
+ void apply(T input) throws Throwable;
+ }
+
public static List toList(Iterable iterable) {
return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
}
- public static Predicate didThrow(Function> consumer) {
+ public static Predicate didThrow(ConsumerWithException consumer) {
return (t) -> {
try {
- consumer.apply(t).get();
+ consumer.apply(t);
return true;
} catch (Throwable e) {
System.err.println(e.getMessage());