Skip to content

Commit

Permalink
Merge pull request #3 from ruokun-niu/main
Browse files Browse the repository at this point in the history
Refresh building comfort sample app
  • Loading branch information
ruokun-niu authored Sep 26, 2024
2 parents e05cadc + f9336c0 commit 37cea6a
Show file tree
Hide file tree
Showing 24 changed files with 464 additions and 287 deletions.
Binary file removed apps/building-comfort/.DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion apps/building-comfort/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-gauge-chart": "^0.4.0",
"react-reactive-graph": "file:../../../src/clients/react",
"react-reactive-graph": "file:../../react",
"react-scripts": "5.0.1",
"rxjs": "^7.5.6",
"uuid": "^8.3.2",
Expand Down
37 changes: 19 additions & 18 deletions apps/building-comfort/app/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,14 @@ initBldQuery();
initFloorQuery();
initUIQuery();

// This function listens to the 'building-comfort-ui' query, which lives in the query-ui.yaml file
function initUIQuery() {
uiListener = new ReactionListener(config.signalRUrl, config.uiQueryId, change => {
if (change.op === 'x') {
return;
}

if (change.op === 'd') {
if (change.payload.before)
removeRoomData(change.payload.before);

else
removeRoomData(change.payload.after);
}
else {
if (change.op !== 'd') { // if op 'u' or 'i'
roomSubject.next(change.payload.after);
upsertRoomData(change.payload.after);
}
Expand Down Expand Up @@ -66,12 +60,12 @@ function initUIQuery() {
});
}

// This function listens to the 'floor-comfort-level-calc' query
function initFloorQuery() {
floorListener = new ReactionListener(config.signalRUrl, config.avgRoomQueryId, change => {
if (change.op === 'x') {
return;
}

floorStats.set(change.payload.after.FloorId, change.payload.after);
floorSubject.next(change.payload.after);
});
Expand All @@ -84,6 +78,7 @@ function initFloorQuery() {
});
}

// This function listens to the 'building-comfort-level-calc' query
function initBldQuery() {
bldListener = new ReactionListener(config.signalRUrl, config.avgFloorQueryId, change => {
if (change.op === 'x') {
Expand Down Expand Up @@ -124,10 +119,6 @@ function upsertRoomData(data) {
floor.rooms.set(data.RoomId, data);
}

function removeRoomData(data) {

}

function App() {
const [data, setData] = React.useState(Array.from(buildings.values()));

Expand All @@ -147,15 +138,16 @@ function Building(props) {

React.useEffect(() => {
let subscription = bldSubject.subscribe(v => {
if (v.BuildingId == props.building.id)
if (v.BuildingId === props.building.id)
setBldComfort(v);
});

return function cleanup() {
subscription.unsubscribe();
};
});


// Init setup
let level = bldComfort.ComfortLevel ?? 0;
return (
<Grid key={props.building.id} container spacing={2} bgcolor="pink">
Expand All @@ -164,12 +156,14 @@ function Building(props) {
<LinearProgress variant="determinate" value={level} color={chooseColor(level)} ></LinearProgress>
{level}
<h3>Comfort Alerts</h3>
{/* If the comfort level of a room is outside of the desired range, a warning will be created here */}
<ReactionResult
url={config.signalRUrl}
queryId={config.roomAlertQueryId}
itemKey={item => item.RoomId}>
<RoomComfortAlert/>
</ReactionResult>
{/* If the comfort level of a floor is outside of the desired range, a warning will be created here */}
<ReactionResult
url={config.signalRUrl}
queryId={config.floorAlertQueryId}
Expand All @@ -179,6 +173,7 @@ function Building(props) {
</Grid>
<Grid item xs={10}>
<Stack spacing={2}>
{/* inits the floors */}
{Array.from(props.building.floors.values()).map(floor =>
<Floor key={floor.id} floor={floor}></Floor>
)}
Expand All @@ -192,10 +187,9 @@ function Building(props) {
function Floor(props) {

const [floorComfort, setFloorComfort] = React.useState(floorStats.has(props.floor.id) ? floorStats.get(props.floor.id) : {});

React.useEffect(() => {
let subscription = floorSubject.subscribe(v => {
if (v.FloorId == props.floor.id)
if (v.FloorId === props.floor.id)
setFloorComfort(v);
});

Expand All @@ -213,6 +207,7 @@ function Floor(props) {
<LinearProgress variant="determinate" value={level} color={chooseColor(level)} ></LinearProgress>
{level}
</Box>
{/* Setup the rooms in the floor */}
{Array.from(props.floor.rooms.values()).map(initRoom => (
<Room key={initRoom.RoomId} initRoom={initRoom}></Room>
))}
Expand All @@ -229,7 +224,8 @@ function Room(props) {

React.useEffect(() => {
let subscription = roomSubject.subscribe(v => {
if (v.RoomId == props.initRoom.RoomId) {
if (v.RoomId === props.initRoom.RoomId) {
// Animation to show the change in values
let prev = buildings.get(props.initRoom.BuildingId)
.floors.get(props.initRoom.FloorId)
.rooms.get(props.initRoom.RoomId);
Expand Down Expand Up @@ -274,29 +270,34 @@ function Room(props) {
CO2: {room.CO2}
</Grid>
</Grid>
{/* This will cause the comfort level to go below the desired range */}
<Button variant="outlined" onClick={e => updateRoom(room.BuildingId, room.FloorId, room.RoomId, 40, 20, 700)}>
Break
</Button>
{/* Resets the comfort level to 46, which is in the desired range of 40-50 */}
<Button variant="outlined" onClick={e => updateRoom(room.BuildingId, room.FloorId, room.RoomId, 70, 40, 10)}>
Reset
</Button>
</Box>
)
}

// This function creates a warning that displays the room name and the comfort level of the room
function RoomComfortAlert(props) {
return (
<Alert severity="warning">{props.RoomName} = {props.ComfortLevel}</Alert>
);
}

// This function creates a warning that displays the floor name and the comfort level of the floor
function FloorComfortAlert(props) {
return (
<Alert severity="warning">{props.FloorName} = {props.ComfortLevel}</Alert>
);
}

async function updateRoom(buildingId, floorId, roomId, temperature, humidity, co2) {
// Sends a POST request to the Backend function to update the temperature, humidity, and CO2 levels of the room
await axios.post(`${config.crudApiUrl}/building/${buildingId}/floor/${floorId}/room/${roomId}/sensor/temp`, { value: temperature });
await delay(200);
await axios.post(`${config.crudApiUrl}/building/${buildingId}/floor/${floorId}/room/${roomId}/sensor/humidity`, { value: humidity });
Expand Down
2 changes: 1 addition & 1 deletion apps/building-comfort/app/src/config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"crudApiUrl": "http://localhost:7071",
"signalRUrl": "http://localhost:5001/hub",
"signalRUrl": "http://localhost:5001/hub",
"avgFloorQueryId": "building-comfort-level-calc",
"uiQueryId": "building-comfort-ui",
"avgRoomQueryId": "floor-comfort-level-calc",
Expand Down
8 changes: 1 addition & 7 deletions apps/building-comfort/devops/data/load_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def print_status_attributes(result):
#
# These responses includes total request units charged and total server latency time.
#
# IMPORTANT: Make sure to consume ALL results returend by cliient tothe final status attributes
# IMPORTANT: Make sure to consume ALL results returend by client tothe final status attributes
# for a request. Gremlin result are stream as a sequence of partial response messages
# where the last response contents the complete status attributes set.
#
Expand All @@ -35,7 +35,6 @@ def create_room(client, building_num, floor_num, room_num):
room_id = f"room_{building_num:02}_{floor_num:02}_{room_num:02}"
room_name = f"Room {floor_num:02}{room_num:02}"
floor_id = f"floor_{building_num:02}_{floor_num:02}"
roomComfort = 50

print(f"Creating Room - room_num:{room_num:02}, room_id: {room_id}, room_name:{room_name}")

Expand All @@ -45,7 +44,6 @@ def create_room(client, building_num, floor_num, room_num):
query += f".property('temp', {config.defaultRoomTemp})"
query += f".property('humidity', {config.defaultRoomHumidity})"
query += f".property('co2', {config.defaultRoomCo2})"
query += f".property('comfortLevel', {roomComfort})"

callback = client.submitAsync(query)
if callback.result() is not None:
Expand All @@ -69,14 +67,12 @@ def create_floor(client, building_num, floor_num):
floor_id = f"floor_{building_num:02}_{floor_num:02}"
floor_name = f"Floor {floor_num:02}"
building_id = f"building_{building_num:02}"
floorComfort = 50

print(f"Creating Floor - floor_num:{floor_num:02}, floor_id: {floor_id}, floor_name:{floor_name}")

# Add Floor Node
query = f"g.addV('Floor').property('id', '{floor_id}')"
query += f".property('name', '{floor_name}')"
query += f".property('comfortLevel', {floorComfort})"

callback = client.submitAsync(query)
if callback.result() is not None:
Expand Down Expand Up @@ -104,13 +100,11 @@ def create_building(client, building_num):

building_id = f"building_{building_num:02}"
building_name = f"Building {building_num:02}"
buildingComfort = 50

print(f"Creating Building - building_num:{building_num:02}, building_id: {building_id}, building_name:{building_name}")

query = f"g.addV('Building').property('id', '{building_id}')"
query += f".property('name', '{building_name}')"
query += f".property('comfortLevel', {buildingComfort})"

callback = client.submitAsync(query)
if callback.result() is not None:
Expand Down
81 changes: 81 additions & 0 deletions apps/building-comfort/devops/drasi/query-alert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Calculates the comfort level of rooms.
# Retrieves all rooms that have a comfort level below 40 or above 50.
# Returns the room ID, room name, and comfort level of each room.
kind: ContinuousQuery
apiVersion: v1
name: room-alert
spec:
mode: query
sources:
subscriptions:
- id: facilities
query: >
MATCH
(r:Room)
WITH
elementId(r) AS RoomId,
r.name AS RoomName,
floor( 50 + (r.temp - 72) + (r.humidity - 42) + CASE WHEN r.co2 > 500 THEN (r.co2 - 500) / 25 ELSE 0 END ) AS ComfortLevel
WHERE ComfortLevel < 40 OR ComfortLevel > 50
RETURN
RoomId, RoomName, ComfortLevel
---
# Calculates the average comfort level of all rooms in a floor
# Retrieves all floors that have a comfort level below 40 or above 50
# Returns the floor ID, floor name and comfort level of each floor
kind: ContinuousQuery
apiVersion: v1
name: floor-alert
spec:
mode: query
sources:
subscriptions:
- id: facilities
query: >
MATCH
(r:Room)-[:PART_OF]->(f:Floor)
WITH
f,
floor( 50 + (r.temp - 72) + (r.humidity - 42) + CASE WHEN r.co2 > 500 THEN (r.co2 - 500) / 25 ELSE 0 END ) AS RoomComfortLevel
WITH
f,
avg(RoomComfortLevel) AS ComfortLevel
WHERE
ComfortLevel < 40 OR ComfortLevel > 50
RETURN
elementId(f) AS FloorId,
f.name AS FloorName,
ComfortLevel
---
# Calculates the average comfort level of all floors in a building
# Returns the building ID, building Name and the comfort level if
# the comfort leve is outside the acceptable range of 40-50
kind: ContinuousQuery
apiVersion: v1
name: building-alert
spec:
mode: query
sources:
subscriptions:
- id: facilities
query: >
MATCH
(r:Room)-[:PART_OF]->(f:Floor)-[:PART_OF]->(b:Building)
WITH
f,
b,
floor( 50 + (r.temp - 72) + (r.humidity - 42) + CASE WHEN r.co2 > 500 THEN (r.co2 - 500) / 25 ELSE 0 END ) AS RoomComfortLevel
WITH
f,
b,
avg(RoomComfortLevel) AS FloorComfortLevel
WITH
b,
avg(FloorComfortLevel) AS ComfortLevel
WHERE
ComfortLevel < 40 OR ComfortLevel > 50
RETURN
elementId(b) AS BuildingId,
b.name AS BuildingName,
ComfortLevel
64 changes: 64 additions & 0 deletions apps/building-comfort/devops/drasi/query-comfort-calc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Calculates the comfort level of the building by taking
# the average of the comfort level of all floors
kind: ContinuousQuery
apiVersion: v1
name: building-comfort-level-calc
spec:
mode: query
sources:
subscriptions:
- id: facilities
query: >
MATCH
(r:Room)-[:PART_OF]->(f:Floor)-[:PART_OF]->(b:Building)
WITH
b,
floor( 50 + (r.temp - 72) + (r.humidity - 42) + CASE WHEN r.co2 > 500 THEN (r.co2 - 500) / 25 ELSE 0 END ) AS RoomComfortLevel
WITH
b,
avg(RoomComfortLevel) AS FloorComfortLevel
WITH
b,
avg(FloorComfortLevel) AS ComfortLevel
RETURN
elementId(b) AS BuildingId,
ComfortLevel
---
# Calculates the comfort level of the floor by taking
# the average of the comfort level of all rooms
kind: ContinuousQuery
apiVersion: v1
name: floor-comfort-level-calc
spec:
mode: query
sources:
subscriptions:
- id: facilities
query: >
MATCH
(r:Room)-[:PART_OF]->(f:Floor)
WITH
f,
floor( 50 + (r.temp - 72) + (r.humidity - 42) + CASE WHEN r.co2 > 500 THEN (r.co2 - 500) / 25 ELSE 0 END ) AS RoomComfortLevel
WITH
f,
avg(RoomComfortLevel) AS ComfortLevel
RETURN
elementId(f) AS FloorId,
ComfortLevel
---
# Calculates the comfort level of a room
kind: ContinuousQuery
apiVersion: v1
name: room-comfort-level-calc
spec:
mode: query
sources:
subscriptions:
- id: facilities
query: >
MATCH
(r:Room)
RETURN
elementId(r) AS RoomId,
floor( 50 + (r.temp - 72) + (r.humidity - 42) + CASE WHEN r.co2 > 500 THEN (r.co2 - 500) / 25 ELSE 0 END ) AS ComfortLevel
Loading

0 comments on commit 37cea6a

Please sign in to comment.