Implemented peer information syncing.

Implemented local sensor sync boolean value validation.
Updated some traces


Former-commit-id: 6cfd254ccb6a983f56308e706f91869867881bc2
This commit is contained in:
Ziver Koc 2016-01-06 23:20:28 +01:00
parent 8d3ac0d340
commit eec7887f94
7 changed files with 199 additions and 65 deletions

View file

@ -1,3 +1,5 @@
http_port=8080 http_port=8080
sync_port=6666 sync_port=6666
# Plugin configurations
tellstick.com_port=COM5 tellstick.com_port=COM5

View file

@ -50,6 +50,15 @@ public class ControllerManager implements HalSensorReportListener, HalEventRepor
/////////////////////////////// SENSORS /////////////////////////////////// /////////////////////////////// SENSORS ///////////////////////////////////
public void register(Sensor sensor) throws IllegalAccessException, InstantiationException { public void register(Sensor sensor) throws IllegalAccessException, InstantiationException {
if(sensor.getSensorData() == null) {
logger.warning("Sensor data is null: "+ sensor);
return;
}
if(!availableSensors.contains(sensor.getSensorData().getClass())) {
logger.warning("Sensor data plugin not available: "+ sensor.getSensorData().getClass());
return;
}
logger.info("Registering new sensor(id: "+ sensor.getId() +"): "+ sensor.getSensorData().getClass()); logger.info("Registering new sensor(id: "+ sensor.getId() +"): "+ sensor.getSensorData().getClass());
Class<? extends HalSensorController> c = sensor.getController(); Class<? extends HalSensorController> c = sensor.getController();
HalSensorController controller = getControllerInstance(c); HalSensorController controller = getControllerInstance(c);
@ -60,6 +69,11 @@ public class ControllerManager implements HalSensorReportListener, HalEventRepor
} }
public void deregister(Sensor sensor){ public void deregister(Sensor sensor){
if(sensor.getSensorData() == null) {
logger.warning("Sensor data is null: "+ sensor);
return;
}
logger.info("Deregistering sensor(id: "+ sensor.getId() +"): "+ sensor.getSensorData().getClass()); logger.info("Deregistering sensor(id: "+ sensor.getId() +"): "+ sensor.getSensorData().getClass());
Class<? extends HalSensorController> c = sensor.getController(); Class<? extends HalSensorController> c = sensor.getController();
HalSensorController controller = (HalSensorController) controllerMap.get(c);; HalSensorController controller = (HalSensorController) controllerMap.get(c);;
@ -114,6 +128,15 @@ public class ControllerManager implements HalSensorReportListener, HalEventRepor
//////////////////////////////// EVENTS /////////////////////////////////// //////////////////////////////// EVENTS ///////////////////////////////////
public void register(Event event) throws IllegalAccessException, InstantiationException { public void register(Event event) throws IllegalAccessException, InstantiationException {
if(event.getEventData() == null) {
logger.warning("Sensor data is null: "+ event);
return;
}
if(!availableEvents.contains(event.getEventData().getClass())) {
logger.warning("Sensor data plugin not available: "+ event.getEventData().getClass());
return;
}
logger.info("Registering new event(id: "+ event.getId() +"): "+ event.getEventData().getClass()); logger.info("Registering new event(id: "+ event.getId() +"): "+ event.getEventData().getClass());
Class<? extends HalEventController> c = event.getController(); Class<? extends HalEventController> c = event.getController();
HalEventController controller = getControllerInstance(c); HalEventController controller = getControllerInstance(c);
@ -124,6 +147,11 @@ public class ControllerManager implements HalSensorReportListener, HalEventRepor
} }
public void deregister(Event event){ public void deregister(Event event){
if(event.getEventData() == null) {
logger.warning("Sensor data is null: "+ event);
return;
}
logger.info("Deregistering event(id: "+ event.getId() +"): "+ event.getEventData().getClass()); logger.info("Deregistering event(id: "+ event.getId() +"): "+ event.getEventData().getClass());
Class<? extends HalEventController> c = event.getController(); Class<? extends HalEventController> c = event.getController();
HalEventController controller = (HalEventController) controllerMap.get(c); HalEventController controller = (HalEventController) controllerMap.get(c);

View file

@ -1,8 +1,7 @@
package se.koc.hal.deamon; package se.koc.hal.deamon;
import se.koc.hal.HalContext; import se.koc.hal.HalContext;
import se.koc.hal.deamon.DataSynchronizationDaemon.SensorDataDTO; import se.koc.hal.deamon.DataSynchronizationDaemon.*;
import se.koc.hal.deamon.DataSynchronizationDaemon.SensorDataListDTO;
import se.koc.hal.intf.HalDaemon; import se.koc.hal.intf.HalDaemon;
import se.koc.hal.struct.Sensor; import se.koc.hal.struct.Sensor;
import se.koc.hal.struct.User; import se.koc.hal.struct.User;
@ -13,6 +12,7 @@ import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.net.ConnectException;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
@ -20,6 +20,7 @@ import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public class DataSynchronizationClient implements HalDaemon { public class DataSynchronizationClient implements HalDaemon {
@ -47,10 +48,33 @@ public class DataSynchronizationClient implements HalDaemon {
ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream()); ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
ObjectInputStream in = new ObjectInputStream(s.getInputStream()); ObjectInputStream in = new ObjectInputStream(s.getInputStream());
// Request peer data
out.writeObject(new PeerDataReqDTO());
PeerDataRspDTO peerData = (PeerDataRspDTO) in.readObject();
user.setUserName(peerData.username);
user.setAddress(peerData.address);
user.save(db);
for(SensorDTO sensorDTO : peerData.sensors){
Sensor sensor = Sensor.getExternalSensor(db, sensorDTO.sensorId);
if(sensor != null) { // new sensor
sensor = new Sensor();
logger.fine("Created new external sensor with external_id: "+ sensorDTO.sensorId);
}
else
logger.fine("Updating external sensor with external_id: "+ sensorDTO.sensorId);
sensor.setExternalId(sensorDTO.sensorId);
sensor.setName(sensorDTO.name);
sensor.setType(sensorDTO.type);
sensor.setConfig(sensorDTO.config);
sensor.save(db);
}
// Request sensor data
List<Sensor> sensors = Sensor.getSensors(db, user); List<Sensor> sensors = Sensor.getSensors(db, user);
for(Sensor sensor : sensors){ for(Sensor sensor : sensors){
if(sensor.isSynced()) { if(sensor.isSynced()) {
PeerDataReqDTO req = new PeerDataReqDTO(); SensorDataReqDTO req = new SensorDataReqDTO();
req.sensorId = sensor.getExternalId(); req.sensorId = sensor.getExternalId();
req.offsetSequenceId = Sensor.getHighestSequenceId(sensor.getId()); req.offsetSequenceId = Sensor.getHighestSequenceId(sensor.getId());
out.writeObject(req); out.writeObject(req);
@ -71,29 +95,36 @@ public class DataSynchronizationClient implements HalDaemon {
else else
logger.fine("Skipped sensor " + sensor.getId()); logger.fine("Skipped sensor " + sensor.getId());
} }
out.writeObject(null); out.writeObject(null); // Tell server we are disconnecting
out.close(); out.close();
in.close(); in.close();
s.close(); s.close();
} catch (UnknownHostException e) { } catch (UnknownHostException|ConnectException e) {
e.printStackTrace(); logger.warning("Unable to connect to: "+ user.getHostname()+":"+user.getPort() +" "+ e.getMessage());
} catch (IOException e) { } catch (ClassNotFoundException|IOException e) {
e.printStackTrace(); logger.log(Level.SEVERE, null, e);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); logger.log(Level.SEVERE, null, e);
} }
} }
/////////////// DTO /////////////////////// /////////////// DTO ///////////////////////
protected static class PeerDataReqDTO implements Serializable{
/**
* Request Peer information and available sensors
*/
protected static class PeerDataReqDTO implements Serializable{}
/**
* Request aggregate data for a specific sensor and offset
*/
protected static class SensorDataReqDTO implements Serializable{
private static final long serialVersionUID = -9066734025245139989L; private static final long serialVersionUID = -9066734025245139989L;
public long sensorId; public long sensorId;

View file

@ -1,8 +1,10 @@
package se.koc.hal.deamon; package se.koc.hal.deamon;
import se.koc.hal.HalContext; import se.koc.hal.HalContext;
import se.koc.hal.deamon.DataSynchronizationClient.PeerDataReqDTO; import se.koc.hal.deamon.DataSynchronizationClient.*;
import se.koc.hal.intf.HalDaemon; import se.koc.hal.intf.HalDaemon;
import se.koc.hal.struct.Sensor;
import se.koc.hal.struct.User;
import zutil.db.DBConnection; import zutil.db.DBConnection;
import zutil.db.SQLResultHandler; import zutil.db.SQLResultHandler;
import zutil.log.LogUtil; import zutil.log.LogUtil;
@ -19,7 +21,9 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public class DataSynchronizationDaemon extends ThreadedTCPNetworkServer implements HalDaemon { public class DataSynchronizationDaemon extends ThreadedTCPNetworkServer implements HalDaemon {
@ -42,7 +46,7 @@ public class DataSynchronizationDaemon extends ThreadedTCPNetworkServer implemen
try { try {
return new DataSynchronizationDaemonThread(s); return new DataSynchronizationDaemonThread(s);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); logger.log(Level.SEVERE, "Unable to create DataSynchronizationDaemonThread", e);
} }
return null; return null;
} }
@ -63,17 +67,40 @@ public class DataSynchronizationDaemon extends ThreadedTCPNetworkServer implemen
public void run(){ public void run(){
logger.fine("User connected: "+ s.getInetAddress().getHostName()); logger.fine("User connected: "+ s.getInetAddress().getHostName());
DBConnection db = HalContext.getDB();
try { try {
Object obj = null; Object obj = null;
while((obj = in.readObject()) != null){ while((obj = in.readObject()) != null){
if(obj instanceof PeerDataReqDTO){ if(obj instanceof PeerDataReqDTO){
PeerDataReqDTO req = (PeerDataReqDTO) obj; logger.fine("Client requesting peer data");
PreparedStatement stmt = HalContext.getDB().getPreparedStatement("SELECT * FROM sensor_data_aggr WHERE sensor_id == ? AND sequence_id > ?"); PeerDataRspDTO rsp = new PeerDataRspDTO();
stmt.setLong(1, req.sensorId); User localUser = User.getLocalUser(db);
rsp.username = localUser.getUserName();
rsp.address = localUser.getAddress();
rsp.sensors = new ArrayList<>();
for(Sensor sensor : Sensor.getLocalSensors(db)){
if(sensor.isSynced()) {
SensorDTO dto = new SensorDTO();
dto.sensorId = sensor.getId();
dto.name = sensor.getName();
dto.type = sensor.getType();
dto.config = sensor.getConfig();
rsp.sensors.add(dto);
}
}
out.writeObject(rsp);
}
if(obj instanceof SensorDataReqDTO){
SensorDataReqDTO req = (SensorDataReqDTO) obj;
Sensor sensor = Sensor.getSensor(db, req.sensorId);
if(sensor.isSynced()) {
PreparedStatement stmt = db.getPreparedStatement("SELECT * FROM sensor_data_aggr WHERE sensor_id == ? AND sequence_id > ?");
stmt.setLong(1, sensor.getId());
stmt.setLong(2, req.offsetSequenceId); stmt.setLong(2, req.offsetSequenceId);
logger.fine("Client requesting: sensorId: "+req.sensorId+", offset: "+req.offsetSequenceId); logger.fine("Client requesting sensor data: sensorId: " + req.sensorId + ", offset: " + req.offsetSequenceId);
SensorDataListDTO list = DBConnection.exec(stmt, new SQLResultHandler<SensorDataListDTO>() { SensorDataListDTO rsp = DBConnection.exec(stmt, new SQLResultHandler<SensorDataListDTO>() {
@Override @Override
public SensorDataListDTO handleQueryResult(Statement stmt, ResultSet result) throws SQLException { public SensorDataListDTO handleQueryResult(Statement stmt, ResultSet result) throws SQLException {
SensorDataListDTO list = new SensorDataListDTO(); SensorDataListDTO list = new SensorDataListDTO();
@ -89,29 +116,45 @@ public class DataSynchronizationDaemon extends ThreadedTCPNetworkServer implemen
return list; return list;
} }
}); });
logger.fine("Sending "+ list.size() +" sensor data items to client"); logger.fine("Sending " + rsp.size() + " sensor data items to client");
out.writeObject(list); out.writeObject(rsp);
}
else{
logger.warning("Client requesting non synced sensor data: sensorId: " + req.sensorId + ", offset: " + req.offsetSequenceId);
SensorDataListDTO rsp = new SensorDataListDTO();
out.writeObject(rsp);
}
} }
} }
out.close(); out.close();
in.close(); in.close();
s.close(); s.close();
} catch (ClassNotFoundException e) {
e.printStackTrace(); } catch (ClassNotFoundException|IOException|SQLException e) {
} catch (IOException e) { logger.log(Level.SEVERE, null, e);
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} }
logger.fine("User disconnected: "+ s.getInetAddress().getHostName()); logger.fine("User disconnected: "+ s.getInetAddress().getHostName());
} }
} }
/////////////// DTO /////////////////////// /////////////// DTO ///////////////////////
protected static class PeerDataRspDTO implements Serializable{
public String username;
public String address;
public ArrayList<SensorDTO> sensors;
}
protected static class SensorDTO implements Serializable{
public long sensorId;
public String name;
public String type;
public String config;
}
protected static class SensorDataListDTO extends ArrayList<SensorDataDTO> implements Serializable{ protected static class SensorDataListDTO extends ArrayList<SensorDataDTO> implements Serializable{
private static final long serialVersionUID = -5701618637734020691L; private static final long serialVersionUID = -5701618637734020691L;
} }
protected static class SensorDataDTO implements Serializable{ protected static class SensorDataDTO implements Serializable{
private static final long serialVersionUID = 8494331502087736809L; private static final long serialVersionUID = 8494331502087736809L;

View file

@ -58,15 +58,16 @@ public class PCConfigureHttpPage extends HalHttpPage {
sensor = new Sensor(); sensor = new Sensor();
sensor.setName(request.get("name")); sensor.setName(request.get("name"));
sensor.setType(request.get("type")); sensor.setType(request.get("type"));
sensor.setSynced(Boolean.parseBoolean(request.get("sync")));
//sensor.setConfig(request.get("config")); //sensor.setConfig(request.get("config"));
sensor.setUser(localUser); sensor.setUser(localUser);
sensor.setSynced(true);
sensor.save(db); sensor.save(db);
case "modify_local_sensor": case "modify_local_sensor":
sensor = Sensor.getSensor(db, id); sensor = Sensor.getSensor(db, id);
if(sensor != null){ if(sensor != null){
sensor.setName(request.get("name")); sensor.setName(request.get("name"));
sensor.setType(request.get("type")); sensor.setType(request.get("type"));
sensor.setSynced(Boolean.parseBoolean(request.get("sync")));
//sensor.setConfig(request.get("config")); //sensor.setConfig(request.get("config"));
sensor.save(db); sensor.save(db);
} }
@ -118,6 +119,8 @@ public class PCConfigureHttpPage extends HalHttpPage {
tmpl.set("extUsers", User.getExternalUsers(db)); tmpl.set("extUsers", User.getExternalUsers(db));
tmpl.set("extSensor", Sensor.getExternalSensors(db)); tmpl.set("extSensor", Sensor.getExternalSensors(db));
tmpl.set("availableSensors", ControllerManager.getInstance().getAvailableSensors());
return tmpl; return tmpl;
} }

View file

@ -50,6 +50,11 @@ public class Sensor extends DBBean{
PreparedStatement stmt = db.getPreparedStatement( "SELECT sensor.* FROM sensor,user WHERE user.external == 1 AND user.id == sensor.user_id" ); PreparedStatement stmt = db.getPreparedStatement( "SELECT sensor.* FROM sensor,user WHERE user.external == 1 AND user.id == sensor.user_id" );
return DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(Sensor.class, db) ); return DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(Sensor.class, db) );
} }
public static Sensor getExternalSensor(DBConnection db, long id) throws SQLException{
PreparedStatement stmt = db.getPreparedStatement( "SELECT sensor.* FROM sensor,user WHERE user.external == 1 AND user.id == sensor.user_id AND sensor.external_id == ?" );
stmt.setLong(1, id);
return DBConnection.exec(stmt, DBBeanSQLResultHandler.create(Sensor.class, db) );
}
public static List<Sensor> getLocalSensors(DBConnection db) throws SQLException{ public static List<Sensor> getLocalSensors(DBConnection db) throws SQLException{
PreparedStatement stmt = db.getPreparedStatement( "SELECT sensor.* FROM sensor,user WHERE user.external == 0 AND user.id == sensor.user_id" ); PreparedStatement stmt = db.getPreparedStatement( "SELECT sensor.* FROM sensor,user WHERE user.external == 0 AND user.id == sensor.user_id" );
@ -67,7 +72,7 @@ public class Sensor extends DBBean{
return DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(Sensor.class, db) ); return DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(Sensor.class, db) );
} }
public static Sensor getSensor(DBConnection db, int id) throws SQLException{ public static Sensor getSensor(DBConnection db, long id) throws SQLException{
return DBBean.load(db, Sensor.class, id); return DBBean.load(db, Sensor.class, id);
} }
@ -81,6 +86,7 @@ public class Sensor extends DBBean{
public void setSensorData(HalSensor sensorData){ public void setSensorData(HalSensor sensorData){
this.sensorData = sensorData; this.sensorData = sensorData;
updateConfig();
} }
public HalSensor getSensorData(){ public HalSensor getSensorData(){
if(sensorData == null) { if(sensorData == null) {
@ -98,7 +104,13 @@ public class Sensor extends DBBean{
return sensorData; return sensorData;
} }
public void save(DBConnection db) throws SQLException { public void save(DBConnection db) throws SQLException {
if(sensorData != null) { if(sensorData != null)
updateConfig();
else
this.config = null;
super.save(db);
}
private void updateConfig(){
try { try {
StringOutputStream buff = new StringOutputStream(); StringOutputStream buff = new StringOutputStream();
JSONObjectOutputStream out = new JSONObjectOutputStream(buff); JSONObjectOutputStream out = new JSONObjectOutputStream(buff);
@ -110,10 +122,6 @@ public class Sensor extends DBBean{
logger.log(Level.SEVERE, "Unable to save sensor data", e); logger.log(Level.SEVERE, "Unable to save sensor data", e);
} }
} }
else
this.config = null;
super.save(db);
}
public String getName() { public String getName() {
@ -128,6 +136,13 @@ public class Sensor extends DBBean{
public void setType(String type) { public void setType(String type) {
this.type = type; this.type = type;
} }
public String getConfig() {
return config;
}
public void setConfig(String config) {
this.config = config;
this.sensorData = null; // invalidate current sensor data object
}
public User getUser() { public User getUser() {
return user; return user;
@ -156,4 +171,5 @@ public class Sensor extends DBBean{
public Class<? extends HalSensorController> getController(){ public Class<? extends HalSensorController> getController(){
return getSensorData().getSensorController(); return getSensorData().getSensorController();
} }
} }

View file

@ -38,6 +38,7 @@
<thead> <thead>
<th>Name</th> <th>Name</th>
<th>Type</th> <th>Type</th>
<th>Public</th>
<th>Configuration</th> <th>Configuration</th>
<th> <th>
<button class="btn btn-default btn-xs pull-right" data-toggle="modal" <button class="btn btn-default btn-xs pull-right" data-toggle="modal"
@ -50,6 +51,7 @@
<tr> <tr>
<td>{{.name}}</td> <td>{{.name}}</td>
<td>{{.type}}</td> <td>{{.type}}</td>
<td>{{.sync}}</td>
<td>{{.config}}</td> <td>{{.config}}</td>
<td> <td>
<form method="POST"> <form method="POST">
@ -62,6 +64,7 @@
data-id="{{.getId()}}" data-id="{{.getId()}}"
data-name="{{.name}}" data-name="{{.name}}"
data-type="{{.type}}" data-type="{{.type}}"
data-sync="{{.sync}}"
data-config="{{.config}}"> data-config="{{.config}}">
<span class="glyphicon glyphicon-pencil"></span> <span class="glyphicon glyphicon-pencil"></span>
</button> </button>
@ -183,17 +186,19 @@
$("#sensorModal").on('show.bs.modal', function (event) { $("#sensorModal").on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); var button = $(event.relatedTarget);
var modal = $(this); var modal = $(this);
modal.find("input").val(""); // Reset all inputs modal.find("input[type=text]").val(""); // Reset all inputs
if(button.data("id") >= 0){ // edit if(button.data("id") >= 0){ // edit
modal.find("input[name=action]").val("modify_local_sensor"); modal.find("input[name=action]").val("modify_local_sensor");
modal.find("input[name=id]").val(button.data("id")); modal.find("input[name=id]").val(button.data("id"));
modal.find("input[name=name]").val(button.data("name")); modal.find("input[name=name]").val(button.data("name"));
modal.find("input[name=type]").val(button.data("type")); modal.find("input[name=type]").val(button.data("type"));
modal.find("input[name=sync]").prop("checked", button.data("sync"));
modal.find("input[name=config]").val(button.data("config")); modal.find("input[name=config]").val(button.data("config"));
} }
else{ // create else{ // create
modal.find("input[name=action]").val("create_local_sensor"); modal.find("input[name=action]").val("create_local_sensor");
modal.find("input[name=id]").val(-1); modal.find("input[name=id]").val(-1);
modal.find("input[name=sync]").prop("checked", "false");
} }
}); });
@ -201,7 +206,7 @@
$("#userModal").on('show.bs.modal', function (event) { $("#userModal").on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); var button = $(event.relatedTarget);
var modal = $(this); var modal = $(this);
modal.find("input").val(""); // Reset all inputs modal.find("input[type=text]").val(""); // Reset all inputs
if(button.data("id") >= 0){ // edit if(button.data("id") >= 0){ // edit
modal.find("input[name=action]").val("modify_external_user"); modal.find("input[name=action]").val("modify_external_user");
modal.find("input[name=id]").val(button.data("id")); modal.find("input[name=id]").val(button.data("id"));
@ -237,10 +242,16 @@
<div class="form-group"> <div class="form-group">
<label class="control-label">Type:</label> <label class="control-label">Type:</label>
<select class="form-control" name="type"> <select class="form-control" name="type">
<option>TellStickController</option> {{#availableSensors}}
<option>RPiLocalController</option> <option>{{.getName()}}</option>
{{/availableSensors}}
</select> </select>
</div> </div>
<div class="form-group">
<label class="control-label">Public:</label>
<input type="checkbox" class="form-control" name="sync" value="true">
</div>
<hr> <hr>
<div class="form-group"> <div class="form-group">
<label class="control-label">Config:</label> <!-- Should be improved, dynamic forms? --> <label class="control-label">Config:</label> <!-- Should be improved, dynamic forms? -->