Merge branch 'feature/sensors_fetch' into 'master'
ARVE sensors data API See merge request cara/caimira!371
This commit is contained in:
commit
139e7ac91d
7 changed files with 165 additions and 10 deletions
|
|
@ -307,6 +307,9 @@ There is one external API call to fetch required information related to the geog
|
|||
The documentation for this geocoding service is available at https://developers.arcgis.com/rest/geocode/api-reference/geocoding-suggest.htm .
|
||||
Please note that there is no need for keys on this API call. It is **free-of-charge**.
|
||||
|
||||
- **Humidity and Inside Temperature:**
|
||||
There is the possibility of using one external API call to fetch information related to a location specified in the UI. The data is related to the inside temperature and humidity taken from an indoor measurement device. Note that the API currently used from ARVE is only available for the `CERN theme` as the authorised sensors are installed at CERN."
|
||||
|
||||
## Update configuration
|
||||
|
||||
If you need to **update** existing configuration, then modify this repository and after having logged in, run:
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import zlib
|
|||
import jinja2
|
||||
import loky
|
||||
from tornado.web import Application, RequestHandler, StaticFileHandler
|
||||
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
|
||||
import tornado.log
|
||||
|
||||
from . import markdown_tools
|
||||
|
|
@ -249,6 +250,51 @@ class ReadmeHandler(BaseRequestHandler):
|
|||
self.finish(readme)
|
||||
|
||||
|
||||
class ArveData(BaseRequestHandler):
|
||||
async def get(self, hotel_id, floor_id):
|
||||
client_id = self.settings["arve_client_id"]
|
||||
client_secret = self.settings['arve_client_secret']
|
||||
arve_api_key = self.settings['arve_api_key']
|
||||
|
||||
http_client = AsyncHTTPClient()
|
||||
|
||||
URL = 'https://arveapi.auth.eu-central-1.amazoncognito.com/oauth2/token'
|
||||
headers = { "Content-Type": "application/x-www-form-urlencoded",
|
||||
"Authorization": b"Basic " + base64.b64encode(f'{client_id}:{client_secret}'.encode())
|
||||
}
|
||||
|
||||
try:
|
||||
response = await http_client.fetch(HTTPRequest(
|
||||
url=URL,
|
||||
method='POST',
|
||||
headers=headers,
|
||||
body="grant_type=client_credentials"
|
||||
),
|
||||
raise_error=True)
|
||||
except Exception as e:
|
||||
print("Something went wrong: %s" % e)
|
||||
|
||||
access_token = json.loads(response.body)['access_token']
|
||||
|
||||
URL = f'https://api.arve.swiss/v1/{hotel_id}/{floor_id}'
|
||||
headers = {
|
||||
"x-api-key": arve_api_key,
|
||||
"Authorization": f'Bearer {access_token}'
|
||||
}
|
||||
try:
|
||||
response = await http_client.fetch(HTTPRequest(
|
||||
url=URL,
|
||||
method='GET',
|
||||
headers=headers,
|
||||
),
|
||||
raise_error=True)
|
||||
except Exception as e:
|
||||
print("Something went wrong: %s" % e)
|
||||
|
||||
self.set_header("Content-Type", 'application/json')
|
||||
return self.finish(response.body)
|
||||
|
||||
|
||||
def make_app(
|
||||
debug: bool = False,
|
||||
calculator_prefix: str = '/calculator',
|
||||
|
|
@ -266,6 +312,7 @@ def make_app(
|
|||
(calculator_prefix + r'/report-json', ConcentrationModelJsonResponse),
|
||||
(calculator_prefix + r'/baseline-model/result', StaticModel),
|
||||
(calculator_prefix + r'/user-guide', ReadmeHandler),
|
||||
(calculator_prefix + r'/api/arve/v1/(.*)/(.*)', ArveData),
|
||||
(calculator_prefix + r'/static/(.*)', StaticFileHandler, {'path': calculator_static_dir}),
|
||||
]
|
||||
|
||||
|
|
@ -298,6 +345,9 @@ def make_app(
|
|||
# COOKIE_SECRET being undefined will result in no login information being
|
||||
# presented to the user.
|
||||
cookie_secret=os.environ.get('COOKIE_SECRET', '<undefined>'),
|
||||
arve_client_id=os.environ.get('ARVE_CLIENT_ID', '<undefined>'),
|
||||
arve_client_secret=os.environ.get('ARVE_CLIENT_SECRET', '<undefined>'),
|
||||
arve_api_key=os.environ.get('ARVE_API_KEY', '<undefined>'),
|
||||
|
||||
# Process parallelism controls. There is a balance between serving a single report
|
||||
# requests quickly or serving multiple requests concurrently.
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class FormData:
|
|||
activity_type: str
|
||||
air_changes: float
|
||||
air_supply: float
|
||||
arve_sensors_option: bool
|
||||
ceiling_height: float
|
||||
exposed_coffee_break_option: str
|
||||
exposed_coffee_duration: int
|
||||
|
|
@ -77,6 +78,7 @@ class FormData:
|
|||
window_width: float
|
||||
windows_number: int
|
||||
window_opening_regime: str
|
||||
sensor_in_use: str
|
||||
short_range_option: str
|
||||
short_range_interactions: list
|
||||
|
||||
|
|
@ -86,6 +88,7 @@ class FormData:
|
|||
'activity_type': 'office',
|
||||
'air_changes': 0.,
|
||||
'air_supply': 0.,
|
||||
'arve_sensors_option': False,
|
||||
'calculator_version': _NO_DEFAULT,
|
||||
'ceiling_height': 0.,
|
||||
'exposed_coffee_break_option': 'coffee_break_0',
|
||||
|
|
@ -109,7 +112,7 @@ class FormData:
|
|||
'infected_lunch_start': '12:30',
|
||||
'infected_people': _NO_DEFAULT,
|
||||
'infected_start': '08:30',
|
||||
'inside_temp': 293.,
|
||||
'inside_temp': _NO_DEFAULT,
|
||||
'location_latitude': _NO_DEFAULT,
|
||||
'location_longitude': _NO_DEFAULT,
|
||||
'location_name': _NO_DEFAULT,
|
||||
|
|
@ -132,6 +135,7 @@ class FormData:
|
|||
'windows_frequency': 60.,
|
||||
'windows_number': 0,
|
||||
'window_opening_regime': 'windows_open_permanently',
|
||||
'sensor_in_use': '',
|
||||
'short_range_option': 'short_range_no',
|
||||
'short_range_interactions': '[]',
|
||||
}
|
||||
|
|
@ -287,14 +291,18 @@ class FormData:
|
|||
volume = self.room_volume
|
||||
else:
|
||||
volume = self.floor_area * self.ceiling_height
|
||||
if self.humidity == '':
|
||||
|
||||
if self.arve_sensors_option == False:
|
||||
if self.room_heating_option:
|
||||
humidity = 0.3
|
||||
else:
|
||||
humidity = 0.5
|
||||
inside_temp = 293.
|
||||
else:
|
||||
humidity = float(self.humidity)
|
||||
room = models.Room(volume=volume, inside_temp=models.PiecewiseConstant((0, 24), (self.inside_temp,)), humidity=humidity)
|
||||
inside_temp = self.inside_temp
|
||||
|
||||
room = models.Room(volume=volume, inside_temp=models.PiecewiseConstant((0, 24), (inside_temp,)), humidity=humidity)
|
||||
|
||||
infected_population = self.infected_population()
|
||||
|
||||
|
|
@ -734,7 +742,7 @@ def baseline_raw_form_data() -> typing.Dict[str, typing.Union[str, float]]:
|
|||
'floor_area': '',
|
||||
'hepa_amount': '250',
|
||||
'hepa_option': '0',
|
||||
'humidity': '',
|
||||
'humidity': '0.5',
|
||||
'infected_coffee_break_option': 'coffee_break_4',
|
||||
'infected_coffee_duration': '10',
|
||||
'infected_dont_have_breaks_with_exposed': '1',
|
||||
|
|
@ -744,7 +752,7 @@ def baseline_raw_form_data() -> typing.Dict[str, typing.Union[str, float]]:
|
|||
'infected_lunch_start': '12:30',
|
||||
'infected_people': '1',
|
||||
'infected_start': '09:00',
|
||||
'inside_temp': 293.,
|
||||
'inside_temp': '293.',
|
||||
'location_latitude': 46.20833,
|
||||
'location_longitude': 6.14275,
|
||||
'location_name': 'Geneva',
|
||||
|
|
|
|||
|
|
@ -271,6 +271,64 @@ function on_wearing_mask_change() {
|
|||
})
|
||||
}
|
||||
|
||||
function populate_temp_hum_values(data, index) {
|
||||
$("#sensor_temperature").text(Math.round(data[index].Details.T) + '°C');
|
||||
$("#sensor_humidity").text(Math.round(data[index].Details.RH) + '%');
|
||||
$("[name='inside_temp']").val(data[index].Details.T + 273.15);
|
||||
$("[name='humidity']").val(data[index].Details.RH/100);
|
||||
};
|
||||
|
||||
//Data from ARVE sensors
|
||||
var DATA_FROM_SENSORS;
|
||||
function show_sensors_data(url) {
|
||||
|
||||
const HOTEL_ID = "CERN"
|
||||
const FLOOR_ID = "1"
|
||||
|
||||
if ($('#sensors > option').length == 0) {
|
||||
$.ajax({
|
||||
url: `${$('#url_prefix').data().calculator_prefix}/api/arve/v1/${HOTEL_ID}/${FLOOR_ID}`,
|
||||
type: 'GET',
|
||||
success: function (result) {
|
||||
DATA_FROM_SENSORS = result;
|
||||
result.map(room => {
|
||||
$("#sensors").append(`<option id=${room.RoomId} value=${room.RoomId}>Sensor ${room.RoomId}</option>`);
|
||||
});
|
||||
populate_temp_hum_values(result, 0);
|
||||
if (url.searchParams.has('sensor_in_use')) {
|
||||
$("#sensors").val(url.searchParams.get('sensor_in_use'));
|
||||
populate_temp_hum_values(result, result.findIndex(function(sensor) {
|
||||
return sensor.RoomId == url.searchParams.get('sensor_in_use');
|
||||
}));
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('Authentication Error - Something went wrong during the authentication process.');
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$("#sensors").change(function (el) {
|
||||
sensor_id = DATA_FROM_SENSORS.findIndex(function(sensor) {
|
||||
return sensor.RoomId == el.target.value
|
||||
});
|
||||
populate_temp_hum_values(DATA_FROM_SENSORS, sensor_id);
|
||||
});
|
||||
|
||||
function on_use_sensors_data_change(url) {
|
||||
sensor_data = $('input[type=radio][name=arve_sensors_option]')
|
||||
sensor_data.each(function (index) {
|
||||
if (this.checked) {
|
||||
getChildElement($(this)).show();
|
||||
show_sensors_data(url);
|
||||
}
|
||||
else {
|
||||
getChildElement($(this)).hide();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function on_short_range_option_change() {
|
||||
short_range = $('input[type=radio][name=short_range_option]')
|
||||
short_range.each(function (index){
|
||||
|
|
@ -404,6 +462,11 @@ function validate_form(form) {
|
|||
});
|
||||
}
|
||||
|
||||
// Logic for the API requests. Always set humity input as the empty string so that we can profit from the "room_heating_option default" values for humidity.
|
||||
if ($("#arve_sensor_no").prop('checked')) {
|
||||
$("[name='humidity']").val('');
|
||||
}
|
||||
|
||||
// Validate location input.
|
||||
if (submit) {
|
||||
// We make the non-visible location inputs mandatory, without marking them as "required" inputs.
|
||||
|
|
@ -706,6 +769,10 @@ $(document).ready(function () {
|
|||
$("#sr_interactions").text(index - 1);
|
||||
}
|
||||
|
||||
else if (name == 'sensor_in_use') {
|
||||
// TODO - Validate if sensor exists
|
||||
}
|
||||
|
||||
//Ignore 0 (default) values from server side
|
||||
else if (!(elemObj.classList.contains("non_zero") || elemObj.classList.contains("remove_zero")) || (value != "0.0" && value != "0")) {
|
||||
elemObj.value = value;
|
||||
|
|
@ -743,6 +810,14 @@ $(document).ready(function () {
|
|||
//Check all radio buttons previously selected
|
||||
$("input[type=radio]:checked").each(function() {require_fields(this)});
|
||||
|
||||
// On CERN theme, when the arve_sensors_option changes we want to make its respective
|
||||
// children show/hide.
|
||||
if ($("input[type=radio][name=arve_sensors_option]").length > 0) {
|
||||
$("input[type=radio][name=arve_sensors_option]").change(on_use_sensors_data_change);
|
||||
// Call the function now to handle forward/back button presses in the browser.
|
||||
on_use_sensors_data_change(url);
|
||||
}
|
||||
|
||||
// When the ventilation_type changes we want to make its respective
|
||||
// children show/hide.
|
||||
$("input[type=radio][name=ventilation_type]").change(on_ventilation_type_change);
|
||||
|
|
@ -841,9 +916,6 @@ $(document).ready(function () {
|
|||
templateSelection: formatLocationSelection
|
||||
});
|
||||
|
||||
// Logic for the API requests. Always set humity input as the empty string so that we can profit from the "room_heating_option default" values for humidity.
|
||||
$("[name='humidity']").val("");
|
||||
|
||||
function formatlocation(suggestedLocation) {
|
||||
// Function is called for each location from the geocoding API.
|
||||
|
||||
|
|
|
|||
|
|
@ -85,8 +85,6 @@
|
|||
</div>
|
||||
{% endblock room_data %}
|
||||
|
||||
<br>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-5">
|
||||
<input type="radio" id="room_data_volume" name="volume_type" value="room_volume_explicit" onclick="require_fields(this)" tabindex="-1" required>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<meta id="url_prefix" data-calculator_prefix="{{ calculator_prefix }}">
|
||||
|
||||
<title>
|
||||
{% block title %}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,27 @@
|
|||
<div data-tooltip="The area you wish to study (choose one of the 2 options). Use GIS Portal or measure. Also indicate if a central (radiator-type) heating system is in use.">
|
||||
<span class="tooltip_text">?</span>
|
||||
</div>
|
||||
|
||||
<div class="split">
|
||||
<div>Use data from ARVE sensors:</div>
|
||||
<div>
|
||||
<input class="ml-2" type="radio" id="arve_sensor_no" name="arve_sensors_option" value=0 checked="checked">
|
||||
<label for="arve_sensor_no">No</label>
|
||||
<input class="ml-2" type="radio" id="arve_sensor_yes" name="arve_sensors_option" value=1 data-enables="#DIVsensors_data">
|
||||
<label for="arve_sensor_yes">Yes</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="DIVsensors_data" style="display:none">
|
||||
<div class="form-group row mb-0">
|
||||
<div class="col-sm-4"><label class="col-form-label">Sensor:</label></div>
|
||||
<div class="col-sm-6">
|
||||
<select id="sensors" name="sensor_in_use" class="form-control">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div><label>Temperature: </label><span class="ml-3 font-weight-bold" id="sensor_temperature"></span></div>
|
||||
<div><label>Relative Humidity: </label><span class="ml-3 font-weight-bold" id="sensor_humidity"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock room_data %}
|
||||
Loading…
Reference in a new issue