15 Commits

Author SHA1 Message Date
Senad Uka
748dd19a87 Ui is completely changed 2016-03-19 09:23:59 +01:00
Senad Uka
21d364bf52 Merge pull request #11 from senaduka/improved_icon_handling_and_pump_management
Added 4 new images for opening and closing out valve while barrell is…
2016-03-19 04:33:23 +01:00
5cd8420bcf Added 4 new images for opening and closing out valve while barrell is either full or not full... also tried to add additional constraints for pump handling in means of that the pump should not be pumping if the out valve is open or opening 2016-03-18 22:26:41 +01:00
Senad Uka
24a1b81b92 Merge pull request #10 from senaduka/barrel_image_handling
Added proper barrel image handling based on input / sensor readings
2016-03-13 19:00:51 +01:00
829d6cf338 Added proper barrel image handling based on input / sensor readings 2016-03-09 00:35:26 +01:00
a8ac425832 Merge pull request #9 from senaduka/in_valve_support
In valve support
2016-03-06 21:07:56 +01:00
Senad Uka
d15bc0e5a6 problem with python syntax 2016-03-06 14:49:26 +01:00
Senad Uka
ee828c544e syntax error fix 2016-03-06 14:47:42 +01:00
Senad Uka
28e5f7f1f0 safe key getting from the response 2016-03-06 14:46:42 +01:00
Senad Uka
ed8267b6ab ignore existing config 2016-03-06 14:14:26 +01:00
Senad Uka
99fdc768fc config/init.py removed 2016-03-06 14:13:25 +01:00
Senad Uka
c47e1be364 Finished in valve support 2016-03-06 14:07:33 +01:00
Senad Uka
8d9e42c147 reformatted code 2016-03-06 13:40:53 +01:00
Senad Uka
279e0f8652 Added suport for in valve 2016-03-06 13:40:53 +01:00
2395cc6ddf Merge pull request #8 from senaduka/optimize_subscriptions
Optimized subscriptions
2016-03-06 12:05:48 +01:00
29 changed files with 280 additions and 134 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
# configuration
controller/config/__init__.py
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]

View File

@@ -24,3 +24,5 @@ selaias:meteor-simpleweather
u2622:persistent-session u2622:persistent-session
percolate:synced-cron percolate:synced-cron
rzymek:moment-locale-bs rzymek:moment-locale-bs
peppelg:bootstrap-3-modal
fortawesome:fontawesome

View File

@@ -28,6 +28,7 @@ ejson@1.0.7
email@1.0.8 email@1.0.8
es5-shim@4.1.14 es5-shim@4.1.14
fastclick@1.0.7 fastclick@1.0.7
fortawesome:fontawesome@4.5.0
geojson-utils@1.0.4 geojson-utils@1.0.4
handlebars@1.0.4 handlebars@1.0.4
hot-code-push@1.0.0 hot-code-push@1.0.0
@@ -58,6 +59,7 @@ npm-bcrypt@0.7.8_2
npm-mongo@1.4.39_1 npm-mongo@1.4.39_1
observe-sequence@1.0.7 observe-sequence@1.0.7
ordered-dict@1.0.4 ordered-dict@1.0.4
peppelg:bootstrap-3-modal@1.0.4
percolate:synced-cron@1.3.0 percolate:synced-cron@1.3.0
promise@0.5.1 promise@0.5.1
random@1.0.5 random@1.0.5

View File

@@ -9,5 +9,6 @@
#bucket_image { #bucket_image {
width: 60%; width: 50%;
cursor: pointer;
} }

View File

@@ -1,6 +1,13 @@
<template name="settings"> <template name="settings">
<div class="col-md-3 col-md-offset-4"> <div class="modal fade">
<h1>Automatsko zaljevanje: </h1> <div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Automatsko zaljevanje</h4>
</div>
<div class="modal-body">
<select name="time_of_day" id="time_of_day"> <select name="time_of_day" id="time_of_day">
<option value="00:00" selected={{ timeSelected "00:00" }}>00:00</option> <option value="00:00" selected={{ timeSelected "00:00" }}>00:00</option>
<option value="00:30" selected={{ timeSelected "00:30" }}>00:30</option> <option value="00:30" selected={{ timeSelected "00:30" }}>00:30</option>
@@ -82,7 +89,15 @@
</div> </div>
<div> <div>
<button id="save_settings" name="save_settings">Zapamti</button>
</div>
</div>
<div class="modal-footer">
<button id="save_settings" class="btn btn-default" name="save_settings" data-dismiss="modal">Zapamti</button>
</div>
</div>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,5 @@
<template name="start"> <template name="start">
<div class="col-lg-12 text-center"> <div class="col-lg-12 text-center form">
{{>state}} {{>state}}
</div> </div>
</template> </template>

View File

@@ -1,24 +1,19 @@
<template name="state"> <template name="state">
<div class="col-md-3 col-md-offset-4"> <div class="col-md-12">
{{#with controller_state}} {{#with controller_state}}
<h1>{{controller_id}}</h1> <img src="{{ bucket_image }}" class="img-responsive center-block" id="bucket_image" />
<img src="{{ bucket_image }}" class="img-responsive center-block" id="bucket_image" /> {{#with last_sensor_reading}} <div class="text-center">
<div> {{controller_id}}:
<strong>{{ temperatureValue }} °C, {{ humidityValue }} % </strong> {{#with last_sensor_reading}} <strong> {{ temperatureValue }} °C, {{ humidityValue }} % </strong> {{/with}}
</div> </div>
<div> <div class="text-center">
Automatsko zaljevanje: <strong>{{ pretty_days config.automaticDaysOfWeek }} {{ pretty_time config.automaticDaysOfWeek config.automaticTimeOfDay }}</strong> <button id="run_settings" class="btn btn-default"> <i class="fa fa-wrench"></i> </button>
</div> </div>
{{/with}} {{/with}}
<div>Otpusni ventil: {{pretty_valve state.out_valve }}</div>
<div>Zadnja komunikacija: {{ last_communication_time }}</div>
{{/with}}
<div> <div>
<button id="water_now" class="{{ water_now_button_class }}">Zalij sada</button> <button id="water_now" class="{{ water_now_button_class }}">Zalij sada</button>
<button id="stop_water_now" class="{{ stop_button_class }}">Prekini zalijevanje</button> <button id="stop_water_now" class="{{ stop_button_class }}">Prekini zalijevanje</button>
</div>
</div>
<div class="col-md-1">
</div> </div>
</div>
</template> </template>

View File

@@ -1,7 +1,6 @@
function controller_state() { function controller_state() {
var controllerId = Session.get('controller_id'); var controllerId = Session.get('controller_id');
result = ControllerState.findOne({}); result = ControllerState.findOne({});
console.log("jupiii", result);
if (!result) { if (!result) {
result = {} result = {}
}; };
@@ -45,37 +44,59 @@ Template.state.helpers({
bucket_image: function() { bucket_image: function() {
var sensor = last_sensor_reading(); var sensor = last_sensor_reading();
if (sensor && sensor.tankFull === "1") { var stateObject = controller_state();
return "/images/barell_full.png"; if (sensor) {
} else { if (sensor.tankFull === 0 && stateObject.state.in_valve === 'open' && stateObject.state.out_valve === 'closed') return "/images/barellFillingUp.png";
return "/images/barell_draining.png"; else if (sensor.tankFull === 1 && (stateObject.state.out_valve === 'closed')) return "/images/barellFull.png";
} else if (sensor.tankFull === 1 && (stateObject.state.out_valve === 'opening')) return "/images/barellStartWateringFull.png";
else if (sensor.tankFull === 1 && (stateObject.state.out_valve === 'open')) return "/images/barellWateringFull.png";
else if (sensor.tankFull === 1 && (stateObject.state.out_valve === 'closing')) return "/images/barellStopWateringFull.png";
else if (sensor.tankFull === 0 && (stateObject.state.out_valve === 'closed')) return "/images/barellNotFull.png";
else if (sensor.tankFull === 0 && (stateObject.state.out_valve === 'opening')) return "/images/barellStartWateringNotFull.png";
else if (sensor.tankFull === 0 && (stateObject.state.out_valve === 'open')) return "/images/barellWateringNotFull.png"
else if (sensor.tankFull === 0 && (stateObject.state.out_valve === 'closing')) return "/images/barellStopWateringNotFull.png"
else return "/images/statusAmber.png";
} else return "/images/statusRed.png";
}, },
last_sensor_reading: last_sensor_reading, last_sensor_reading: last_sensor_reading,
last_communication_time: function() {
return moment(controller_state().time).fromNow();
},
water_now_button_class: function() { water_now_button_class: function() {
var stateObject = controller_state(); var stateObject = controller_state();
if (stateObject.state && (stateObject.state.out_valve === 'open' || stateObject.state.out_valve === 'opening')) { if (stateObject.state && (stateObject.state.out_valve === 'open' || stateObject.state.out_valve === 'opening')) {
return 'hidden'; return 'hidden btn btn-success';
} else { } else {
return ''; return 'btn btn-success';
} }
}, },
stop_button_class: function() { stop_button_class: function() {
var stateObject = controller_state(); var stateObject = controller_state();
if (stateObject.state && (stateObject.state.out_valve === 'closed' || stateObject.state.out_valve === 'closing')) { if (stateObject.state && (stateObject.state.out_valve === 'closed' || stateObject.state.out_valve === 'closing')) {
return 'hidden'; return 'hidden btn btn-success';
} else { } else {
return ''; return 'btn btn-success';
}
} }
},
pretty_days: function(daysInNumbers) {
var days = ["Nedjelja", "Ponedjeljak", "Utorak", "Srijeda", "Četvrtak", "Petak", "Subota"];
if (daysInNumbers.length == 7) {
return "Svaki dan"
} else if (!daysInNumbers || daysInNumbers.length == 0) {
return "Nikad"
} else {
return daysInNumbers.map(function(number) {
return days[number -1 ];
}).join(", ");
}
},
pretty_time: function(daysInNumbers, time) {
if (!daysInNumbers || daysInNumbers.length == 0) {
return ""
} else {
return " u " + time;
}
}
}); });
Template.state.events({ Template.state.events({
@@ -87,6 +108,14 @@ Template.state.events({
'click #stop_water_now': function() { 'click #stop_water_now': function() {
var controller_id = Session.get('controller_id'); var controller_id = Session.get('controller_id');
Meteor.call('closeOutValve', controller_id) Meteor.call('closeOutValve', controller_id)
},
'click #run_settings': function() {
Modal.show('settings');
},
'click #bucket_image': function() {
Modal.show('state_details', controller_state());
} }
}); });

View File

@@ -0,0 +1,24 @@
<template name="state_details">
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Detalji</h4>
</div>
<div class="modal-body">
<div><strong>Otpusni ventil:</strong> {{pretty_valve state.out_valve }}</div>
<div><strong>Ulazni ventil/pumpa:</strong> {{pretty_valve state.in_valve }}</div>
<div><strong>Zadnja komunikacija: {{ last_communication_time }}</strong></div>
</div>
<div class="modal-footer">
<button id="close_details" class="btn btn-default" name="close_details" data-dismiss="modal">Zatvori</button>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,14 @@
Template.state_details.helpers({
pretty_valve: function(state) {
if (state === 'open') return "Otvoren";
if (state === 'opening') return "Otvara se";
if (state === 'closing') return "Zatvara se";
if (state === 'closed') return "Zatvoren";
},
last_communication_time: function() {
return moment(this.time).fromNow();
}
});
Template.state_details.events({
});

View File

@@ -4,7 +4,6 @@
<li role="presentation" class="{{ class_for 'start' }}"><a href="#">Stanje</a></li> <li role="presentation" class="{{ class_for 'start' }}"><a href="#">Stanje</a></li>
<li role="presentation" class="{{ class_for 'weather' }}"><a href="#">Vrijeme</a></li> <li role="presentation" class="{{ class_for 'weather' }}"><a href="#">Vrijeme</a></li>
<li role="presentation" class="{{ class_for 'log' }}"><a href="#">Novosti</a></li> <li role="presentation" class="{{ class_for 'log' }}"><a href="#">Novosti</a></li>
<li role="presentation" class="{{ class_for 'settings' }}"><a href="#">Podešavanje</a></li>
<li role="presentation" class="controller_selection"> <input type="number" id="controller" name="controller" value="{{ selected_controller }}" min="1" max="99999"> <button id="switch" name="switch">Prebaci</button> <li role="presentation" class="controller_selection"> <input type="number" id="controller" name="controller" value="{{ selected_controller }}" min="1" max="99999"> <button id="switch" name="switch">Prebaci</button>
</li> </li>
</ul> </ul>

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -9,7 +9,7 @@ Api.addRoute('sensorData', {
authRequired: false authRequired: false
}, { }, {
post: function() { post: function() {
console.log("Body params", this.bodyParams); reactToSensorData(this.bodyParams);
SensorData.insert({ SensorData.insert({
temperatureValue: parseFloat(this.bodyParams.temperatureValue), temperatureValue: parseFloat(this.bodyParams.temperatureValue),
humidityValue: parseFloat(this.bodyParams.humidityValue), humidityValue: parseFloat(this.bodyParams.humidityValue),
@@ -23,16 +23,49 @@ Api.addRoute('sensorData', {
}); });
function reactToSensorData(nextSensorReading) {
console.log("reacting to sensor");
var controllerId = nextSensorReading.controllerId;
var state = stateOrDefault(controllerId).state;
var shouldStartPumping = (!state.in_valve || state.in_valve === 'closed') && ((parseInt(nextSensorReading.tankFull) === 0) && (state.out_valve === 'closed' || state.out_valve === 'closing'));
if (shouldStartPumping) {
ControllerState.update({
controller_id: controllerId
}, {
'$set': {
'state.in_valve': 'opening',
'time': new Date(),
'set_by': 'server'
}
});
}
var shouldStopPumping = (state.in_valve === 'open' || state.in_valve === 'opening') && (parseInt(nextSensorReading.tankFull) === 1 || state.out_valve === 'open' || state.out_valve === 'opening');
if (shouldStopPumping) {
ControllerState.update({
controller_id: controllerId
}, {
'$set': {
'state.in_valve': 'closing',
'time': new Date(),
'set_by': 'server'
}
});
}
}
Api.addRoute('state/:id', { Api.addRoute('state/:id', {
authRequired: false authRequired: false
}, { }, {
post: function() { post: function() {
console.log("Body params", this.bodyParams); console.log("setting state", this.bodyParams);
return ControllerState.update({ return ControllerState.update({
controller_id: this.urlParams.id controller_id: this.urlParams.id
}, { }, {
'$set': { '$set': {
'state.out_valve': this.bodyParams.out_valve, 'state.out_valve': this.bodyParams.out_valve,
'state.in_valve': this.bodyParams.in_valve,
'time': new Date(), 'time': new Date(),
'set_by': 'client' 'set_by': 'client'
} }
@@ -43,6 +76,7 @@ Api.addRoute('state/:id', {
} }
}); });
function stateOrDefault(id) { function stateOrDefault(id) {
var stateEntry = ControllerState.findOne({ var stateEntry = ControllerState.findOne({
controller_id: id, controller_id: id,
@@ -52,13 +86,13 @@ function stateOrDefault(id) {
stateEntry = ControllerState.insert({ stateEntry = ControllerState.insert({
controller_id: id, controller_id: id,
state: { state: {
out_valve: 'closed' out_valve: 'closed',
in_valve: 'closed'
}, },
time: new Date(), time: new Date(),
config: { config: {
draining_period_amount: 5, draining_period_amount: 60,
draining_period_unit: 'minutes' draining_period_unit: 'minutes'
}, },
set_by: 'server' set_by: 'server'
}); });

View File

@@ -3,7 +3,8 @@
GPIO_PIN_DHT = 4 # BCM GPIO_PIN_DHT = 4 # BCM
SENSORDATA_URL = 'http://tfm.meteor.com/api/v1.0/sensorData' SENSORDATA_URL = 'http://tfm.meteor.com/api/v1.0/sensorData'
GPIO_PIN_TANKFULL = 20 # BCM GPIO_PIN_TANKFULL = 20 # BCM
GPIO_PIN_VALVE = 21 # BCM GPIO_PIN_OUT_VALVE = 21 # BCM
GPIO_PIN_IN_VALVE = 18 # BCM
API_BASE_URL = 'http://tfm.meteor.com/api/v1.0' API_BASE_URL = 'http://tfm.meteor.com/api/v1.0'
CONTROLLER_ID = '120' # every controller must have a different one CONTROLLER_ID = '120' # every controller must have a different one
STATE_FILE = '/var/run/controller_state' STATE_FILE = '/var/run/controller_state'

View File

@@ -7,29 +7,56 @@ class Changer(object):
self.local_state = local_state self.local_state = local_state
self.remote_state = remote_state self.remote_state = remote_state
GPIO.setmode(GPIO.BCM) # Broadcom pin-numbering scheme GPIO.setmode(GPIO.BCM) # Broadcom pin-numbering scheme
GPIO.setup(config.GPIO_PIN_VALVE, GPIO.OUT) GPIO.setup(config.GPIO_PIN_OUT_VALVE, GPIO.OUT)
GPIO.setup(config.GPIO_PIN_IN_VALVE, GPIO.OUT)
self.states = {
'opening': self.open_valve,
'closing': self.close_valve,
'open': self.open_valve,
'closed': self.close_valve
self.out_valve_states = {
'opening': self.open_out_valve,
'closing': self.close_out_valve,
'open': self.open_out_valve,
'closed': self.close_out_valve
} }
self.in_valve_states = {
'opening': self.open_in_valve,
'closing': self.close_in_valve,
'open': self.open_in_valve,
'closed': self.close_in_valve
}
def safe_remote_state(self, key):
if key in ['out_valve', 'in_valve']:
return self.remote_state.get(key, 'closed')
else:
return self.remote_state.get(key,'');
def process_change(self): def process_change(self):
self.validate_states() self.validate_states()
change = self.states.get(self.remote_state['out_valve'], None )
if change is not None: out_valve_change = self.out_valve_states.get(self.safe_remote_state('out_valve'), None )
change() if out_valve_change is not None:
out_valve_change()
in_valve_change = self.in_valve_states.get(self.safe_remote_state('in_valve'), None )
if in_valve_change is not None:
in_valve_change()
return self.local_state return self.local_state
def open_valve(self): def open_in_valve(self):
GPIO.output(config.GPIO_PIN_VALVE, GPIO.HIGH) GPIO.output(config.GPIO_PIN_IN_VALVE, GPIO.HIGH)
self.local_state['in_valve'] = 'open'
def close_in_valve(self):
GPIO.output(config.GPIO_PIN_IN_VALVE, GPIO.LOW)
self.local_state['in_valve'] = 'closed'
def open_out_valve(self):
GPIO.output(config.GPIO_PIN_OUT_VALVE, GPIO.HIGH)
self.local_state['out_valve'] = 'open' self.local_state['out_valve'] = 'open'
def close_valve(self): def close_out_valve(self):
GPIO.output(config.GPIO_PIN_VALVE, GPIO.LOW) GPIO.output(config.GPIO_PIN_OUT_VALVE, GPIO.LOW)
self.local_state['out_valve'] = 'closed' self.local_state['out_valve'] = 'closed'
def validate_states(self): def validate_states(self):