73 Commits

Author SHA1 Message Date
66442bf4de fixed a bug in rendering the correct picture, due to changes of multiple levels 2016-06-19 00:59:25 +02:00
78f052a664 - failed temp and humidity reading wont prevent data sending
- printing of water level
2016-06-12 12:03:19 +02:00
Senad Uka
8fc028aaab fixed duplicate controller folder 2016-06-11 18:56:47 +02:00
Senad Uka
cc7aebc6f9 Merge pull request #23 from senaduka/config_pumping_levels_and_draining_period
Config pumping levels and draining period
2016-06-10 10:01:39 +02:00
e5d4c820d6 set both start and stop pumping levels by default to full 2016-06-09 21:56:26 +02:00
cfd7d677dd deleted draining period and amount from config file as this does not work like i thought it would 2016-06-09 20:56:42 +02:00
4872f4a910 expanded the water levels to be used to configure pumping levels and moved the water draining period to config file 2016-06-09 20:07:38 +02:00
Senad Uka
5c1f1629e7 Update __init__.py
removing canceling shutdown
2016-06-08 18:50:32 +02:00
46af39c7af Update README.md
changed the crontab info in means of changing
*/15 * * * * /usr/bin/python /home/pi/projects/tfm/controller/sensors.py "Automatski, Senad Uka" 120
*/1 * * * * /usr/bin/python /home/pi/projects/tfm/controller/sync_state.py "Automatski, Senad Uka" 120
to 
*/5 * * * * cd /home/pi/projects/tfm/controller && sh activity.sh
2016-06-08 13:18:08 +02:00
bcb84e4400 Update copy__init__.py.example 2016-06-08 13:12:36 +02:00
79651cb6af Merge pull request #22 from senaduka/local_safe_autonomy
Local safe autonomy
2016-06-08 11:15:04 +02:00
Senad Uka
bffab2bf26 Merge pull request #17 from senaduka/shell_script_to_unify_activities
created activity.sh to unify all activities in a specific order
2016-06-08 11:14:33 +02:00
Senad Uka
718968f57d readme 2016-06-08 10:45:32 +02:00
Senad Uka
f1d7232c10 network check script 2016-06-08 10:34:05 +02:00
Senad Uka
fec689a5bc dweet fix again 2016-06-08 10:03:17 +02:00
Senad Uka
533135013b dweet fix again 2016-06-08 10:01:55 +02:00
Senad Uka
e0e0f0d24c dweet fix 2016-06-08 09:48:36 +02:00
Senad Uka
b30af162a1 reboot time 2016-06-08 09:38:54 +02:00
Senad Uka
c11e6b29b1 dweet exception 2016-06-08 09:31:51 +02:00
Senad Uka
d2a2fae936 dweet source code 2016-06-08 09:30:06 +02:00
Senad Uka
a12a218e85 reboot canceling 2016-06-08 09:28:42 +02:00
Senad Uka
f8a8284944 added dweet and reboot 2016-06-08 09:16:44 +02:00
Senad Uka
f9defb8e2a fixed syntax error 4 2016-06-08 08:39:31 +02:00
Senad Uka
5ae36641a2 fixed syntax error 3 2016-06-08 08:31:58 +02:00
Senad Uka
62f9cabb83 fixed syntax error 2 2016-06-08 08:31:42 +02:00
Senad Uka
91027c5330 fixed syntax error 2016-06-08 08:28:48 +02:00
Senad Uka
7d434625ca Local autonomy test 1 2016-06-08 08:24:43 +02:00
Senad Uka
61bb6d0d4b Merge pull request #21 from senaduka/multipleWaterLevelSensors
Multiple level sensors added
2016-06-05 11:14:25 +02:00
1ae13f6fb1 Multiple level sensors added 2016-06-02 23:13:42 +02:00
db843163f2 Merge pull request #20 from senaduka/last_fill_time
Disabled temperature graph
2016-05-17 16:12:53 +02:00
Senad Uka
d2db89ffc0 Disabled temperature graph 2016-05-17 07:05:54 +02:00
336b82be3f Merge pull request #19 from senaduka/last_fill_time
Added last valve open time
2016-05-08 06:06:31 +02:00
Senad Uka
ac76654e2a Added last valve open time 2016-05-08 05:17:12 +02:00
2772baafbc Merge pull request #18 from senaduka/add_low_temperature
Lower temperature forecast
2016-05-02 18:13:22 +02:00
Senad Uka
3e0733bf3d Lower temperature forecast 2016-05-02 18:10:54 +02:00
Senad Uka
d9fa0bf2f5 Calculator done 2016-05-02 15:18:02 +02:00
Senad Uka
abff71ac06 Calculator done 2016-05-02 14:09:09 +02:00
90e43b8bd2 created activity.sh to unify all activities in a specific order 2016-04-23 14:32:31 +00:00
Senad Uka
5a4fdbbd00 reversed logic 2016-04-10 12:11:54 +02:00
Senad Uka
96c67a6d00 gitignore 2016-04-10 11:58:18 +02:00
Senad Uka
3b8fb3dce4 fixed bug with rpio 2016-04-10 11:57:13 +02:00
0b4a886de6 Merge pull request #16 from senaduka/add_weather_again
weather works
2016-04-10 10:56:23 +02:00
Senad Uka
79df9d05b4 weather works 2016-04-10 10:47:11 +02:00
4df30c3523 Merge pull request #15 from senaduka/graph_fixing
FIxed graphs / inverted the tankFull bit!
2016-04-09 09:48:02 +02:00
Senad Uka
f886530f5f upgraded mongo and packages 2016-04-09 07:51:11 +02:00
Senad Uka
98983ea50d FIxed bug with inverted the tankFull bit! 2016-04-09 07:29:24 +02:00
Senad Uka
c947980844 FIxed graphs / inverted the tankFull bit! 2016-04-09 07:11:08 +02:00
974f18e067 Merge pull request #14 from senaduka/pre_production_fixes
Pre production fixes
2016-03-26 10:29:01 +01:00
Senad Uka
534497e373 Fixed filling logic 2016-03-26 07:32:53 +01:00
Senad Uka
26052a9afc Created graphs without d3 ;) 2016-03-26 07:28:43 +01:00
Senad Uka
e9e808a1bc Merge pull request #13 from senaduka/image_vise
- fixed names to match barrell*
2016-03-25 20:19:17 +01:00
4197d06819 - fixed names to match barrell*
- added more tag to images
2016-03-22 21:54:13 +01:00
Senad Uka
36d1adc626 Fixed a typo 2016-03-20 08:00:14 +01:00
Senad Uka
2317ad3e61 Some css modifications 2016-03-20 07:54:20 +01:00
b2e619c2c9 Merge pull request #12 from senaduka/ui_modifications
Ui is completely changed
2016-03-19 09:30:36 +01:00
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
Senad Uka
357acd6f91 Optimized subscriptions 2016-03-06 09:20:36 +01:00
Senad Uka
6ea9933d60 performance update 2016-02-28 11:19:18 +01:00
7984783cdf Merge pull request #7 from senaduka/add_scheduling_setup
added scheduling
2016-02-28 10:22:47 +01:00
54 changed files with 1060 additions and 290 deletions

6
.gitignore vendored
View File

@@ -1,3 +1,6 @@
# configuration
controller/config/__init__.py
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
@@ -55,3 +58,6 @@ docs/_build/
# PyBuilder
target/
# meteor
app.tar.gz

View File

@@ -10,3 +10,4 @@ notices-for-facebook-graph-api-2
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package

View File

@@ -12,12 +12,9 @@ session # Client-side reactive dictionary for your app
jquery # Helpful client-side library
tracker # Meteor's client-side reactive programming library
standard-minifiers # JS/CSS minifiers run for production mode
es5-shim # ECMAScript 5 compatibility for older browsers.
ecmascript # Enable ECMAScript2015+ syntax in app code
autopublish # Publish all data to the clients (for prototyping)
insecure # Allow all DB writes from clients (for prototyping)
less
huttonr:bootstrap3
nimble:restivus
@@ -26,3 +23,8 @@ selaias:meteor-simpleweather
u2622:persistent-session
percolate:synced-cron
rzymek:moment-locale-bs
peppelg:bootstrap-3-modal
fortawesome:fontawesome
mfpierre:chartist-js
standard-minifier-css
standard-minifier-js

View File

@@ -1 +1 @@
METEOR@1.2.1
METEOR@1.3.1

View File

@@ -1,90 +1,95 @@
accounts-base@1.2.2
accounts-password@1.1.4
accounts-base@1.2.5
accounts-password@1.1.7
allow-deny@1.0.3
amplify@1.0.0
autopublish@1.0.4
autoupdate@1.2.4
babel-compiler@5.8.24_1
babel-runtime@0.1.4
base64@1.0.4
binary-heap@1.0.4
blaze@2.1.3
blaze-html-templates@1.0.1
blaze-tools@1.0.4
boilerplate-generator@1.0.4
caching-compiler@1.0.0
caching-html-compiler@1.0.2
callback-hook@1.0.4
check@1.1.0
coffeescript@1.0.11
ddp@1.2.2
ddp-client@1.2.1
ddp-common@1.2.2
ddp-rate-limiter@1.0.0
ddp-server@1.2.2
deps@1.0.9
diff-sequence@1.0.1
ecmascript@0.1.6
ecmascript-runtime@0.2.6
ejson@1.0.7
email@1.0.8
es5-shim@4.1.14
fastclick@1.0.7
geojson-utils@1.0.4
handlebars@1.0.4
hot-code-push@1.0.0
html-tools@1.0.5
htmljs@1.0.5
http@1.1.1
huttonr:bootstrap3@3.3.6_6
huttonr:bootstrap3-assets@3.3.6_2
id-map@1.0.4
insecure@1.0.4
jquery@1.11.4
json@1.0.3
launch-screen@1.0.4
less@2.5.1
livedata@1.0.15
localstorage@1.0.5
logging@1.0.8
meteor@1.1.10
meteor-base@1.0.1
minifiers@1.1.7
minimongo@1.0.10
mobile-experience@1.0.1
mobile-status-bar@1.0.6
momentjs:moment@2.11.2
mongo@1.1.3
mongo-id@1.0.1
nimble:restivus@0.8.7
autoupdate@1.2.7
babel-compiler@6.6.1
babel-runtime@0.1.7
base64@1.0.7
binary-heap@1.0.7
blaze@2.1.6
blaze-html-templates@1.0.3
blaze-tools@1.0.7
boilerplate-generator@1.0.7
caching-compiler@1.0.3
caching-html-compiler@1.0.5
callback-hook@1.0.7
check@1.1.3
coffeescript@1.0.16
ddp@1.2.4
ddp-client@1.2.4
ddp-common@1.2.4
ddp-rate-limiter@1.0.3
ddp-server@1.2.5
deps@1.0.11
diff-sequence@1.0.4
ecmascript@0.4.2
ecmascript-runtime@0.2.9
ejson@1.0.10
email@1.0.11
es5-shim@4.5.9
fastclick@1.0.10
fortawesome:fontawesome@4.5.0
fourseven:scss@3.4.1
geojson-utils@1.0.7
hot-code-push@1.0.3
html-tools@1.0.8
htmljs@1.0.8
http@1.1.4
huttonr:bootstrap3@3.3.6_10
huttonr:bootstrap3-assets@3.3.6_3
id-map@1.0.6
jquery@1.11.7
launch-screen@1.0.10
less@2.5.7
livedata@1.0.17
localstorage@1.0.8
logging@1.0.11
meteor@1.1.13
meteor-base@1.0.3
mfpierre:chartist-js@1.6.1
minifier-css@1.1.10
minifier-js@1.1.10
minimongo@1.0.13
mobile-experience@1.0.3
mobile-status-bar@1.0.11
modules@0.5.2
modules-runtime@0.6.2
momentjs:moment@2.12.0
mongo@1.1.6
mongo-id@1.0.3
nimble:restivus@0.8.10
npm-bcrypt@0.7.8_2
npm-mongo@1.4.39_1
observe-sequence@1.0.7
ordered-dict@1.0.4
percolate:synced-cron@1.3.0
promise@0.5.1
random@1.0.5
rate-limit@1.0.0
reactive-dict@1.1.3
reactive-var@1.0.6
reload@1.1.4
retry@1.0.4
routepolicy@1.0.6
npm-mongo@1.4.42
observe-sequence@1.0.10
ordered-dict@1.0.6
peppelg:bootstrap-3-modal@1.0.4
percolate:synced-cron@1.3.2
promise@0.6.6
random@1.0.8
rate-limit@1.0.3
reactive-dict@1.1.6
reactive-var@1.0.8
reload@1.1.7
retry@1.0.6
routepolicy@1.0.9
rzymek:moment-locale-bs@2.9.0
selaias:meteor-simpleweather@0.6.8
service-configuration@1.0.5
session@1.1.1
sha@1.0.4
simple:json-routes@2.0.1
spacebars@1.0.7
spacebars-compiler@1.0.7
srp@1.0.4
standard-minifiers@1.0.2
templating@1.1.5
templating-tools@1.0.0
tracker@1.0.9
selaias:meteor-simpleweather@0.7.0
service-configuration@1.0.8
session@1.1.4
sha@1.0.6
simple:json-routes@2.1.0
spacebars@1.0.10
spacebars-compiler@1.0.10
srp@1.0.7
standard-minifier-css@1.0.5
standard-minifier-js@1.0.5
templating@1.1.8
templating-tools@1.0.3
tracker@1.0.12
u2622:persistent-session@0.4.4
ui@1.0.8
underscore@1.0.4
url@1.0.5
webapp@1.2.3
webapp-hashing@1.0.5
ui@1.0.10
underscore@1.0.7
url@1.0.8
webapp@1.2.7
webapp-hashing@1.0.8

View File

@@ -2,12 +2,20 @@
padding: 40px 15px;
text-align: center;
}
.controller_selection {
padding-top: 10px;
}
#bucket_image {
width: 30%;
@media all and (orientation: portrait) {
#bucket_image {
width: 90%;
cursor: pointer;
}
}
@media all and (orientation: landscape) {
#bucket_image {
width: 40%;
cursor: pointer;
}
}

View File

@@ -5,7 +5,8 @@ function sensor_data_collection() {
}, {
sort: {
created_at: -1
}
},
limit: 100
});
}

View File

@@ -1,3 +1,3 @@
<template name="sensorData">
<li>{{owner}} / <strong>{{temperatureValue}}°C</strong> / <strong>{{humidityValue}}%</strong> / Bačva puna: <strong>{{tankFull}}</strong> / {{created_at_formatted}}</li>
<li>{{owner}} / <strong>{{temperatureValue}}°C</strong> / <strong>{{humidityValue}}%</strong> / Bačva puna: <strong>{{tankFull}}</strong> (L0:{{tankLevel0}}-L1:{{tankLevel1}}-L2:{{tankLevel2}}-L3:{{tankLevel3}}-L4:{{tankLevel4}}-full:{{tankFull}}) / {{created_at_formatted}}</li>
</template>

View File

@@ -1,88 +1,103 @@
<template name="settings">
<div class="col-md-3 col-md-offset-4">
<h1>Automatsko zaljevanje: </h1>
<select name="time_of_day" id="time_of_day">
<option value="00:00" selected={{ timeSelected "00:00" }}>00:00</option>
<option value="00:30" selected={{ timeSelected "00:30" }}>00:30</option>
<option value="01:00" selected={{ timeSelected "01:00" }}>01:00</option>
<option value="01:30" selected={{ timeSelected "01:30" }}>01:30</option>
<option value="02:00" selected={{ timeSelected "02:00" }}>02:00</option>
<option value="02:30" selected={{ timeSelected "02:30" }}>02:30</option>
<option value="03:00" selected={{ timeSelected "03:00" }}>03:00</option>
<option value="03:30" selected={{ timeSelected "03:30" }}>03:30</option>
<option value="04:00" selected={{ timeSelected "04:00" }}>04:00</option>
<option value="04:30" selected={{ timeSelected "04:30" }}>04:30</option>
<option value="05:00" selected={{ timeSelected "05:00" }}>05:00</option>
<option value="05:30" selected={{ timeSelected "05:30" }}>05:30</option>
<option value="06:00" selected={{ timeSelected "06:00" }}>06:00</option>
<option value="06:30" selected={{ timeSelected "06:30" }}>06:30</option>
<option value="07:00" selected={{ timeSelected "07:00" }}>07:00</option>
<option value="07:30" selected={{ timeSelected "07:30" }}>07:30</option>
<option value="08:00" selected={{ timeSelected "08:00" }}>08:00</option>
<option value="08:30" selected={{ timeSelected "08:30" }}>08:30</option>
<option value="09:00" selected={{ timeSelected "09:00" }}>09:00</option>
<option value="09:30" selected={{ timeSelected "09:30" }}>09:30</option>
<option value="10:00" selected={{ timeSelected "10:00" }}>10:00</option>
<option value="10:30" selected={{ timeSelected "10:30" }}>10:30</option>
<option value="11:00" selected={{ timeSelected "11:00" }}>11:00</option>
<option value="11:30" selected={{ timeSelected "11:30" }}>11:30</option>
<option value="12:00" selected={{ timeSelected "12:00" }}>12:00</option>
<option value="12:30" selected={{ timeSelected "12:30" }}>12:30</option>
<option value="13:00" selected={{ timeSelected "13:00" }}>13:00</option>
<option value="13:30" selected={{ timeSelected "13:30" }}>13:30</option>
<option value="14:00" selected={{ timeSelected "14:00" }}>14:00</option>
<option value="14:30" selected={{ timeSelected "14:30" }}>14:30</option>
<option value="15:00" selected={{ timeSelected "15:00" }}>15:00</option>
<option value="15:30" selected={{ timeSelected "15:30" }}>15:30</option>
<option value="16:00" selected={{ timeSelected "16:00" }}>16:00</option>
<option value="16:30" selected={{ timeSelected "16:30" }}>16:30</option>
<option value="17:00" selected={{ timeSelected "17:00" }}>17:00</option>
<option value="17:30" selected={{ timeSelected "17:30" }}>17:30</option>
<option value="18:00" selected={{ timeSelected "18:00" }}>18:00</option>
<option value="18:30" selected={{ timeSelected "18:30" }}>18:30</option>
<option value="19:00" selected={{ timeSelected "19:00" }}>19:00</option>
<option value="19:30" selected={{ timeSelected "19:30" }}>19:30</option>
<option value="20:00" selected={{ timeSelected "20:00" }}>20:00</option>
<option value="20:30" selected={{ timeSelected "20:30" }}>20:30</option>
<option value="21:00" selected={{ timeSelected "21:00" }}>21:00</option>
<option value="21:30" selected={{ timeSelected "21:30" }}>21:30</option>
<option value="22:00" selected={{ timeSelected "22:00" }}>22:00</option>
<option value="22:30" selected={{ timeSelected "22:30" }}>22:30</option>
<option value="23:00" selected={{ timeSelected "23:00" }}>23:00</option>
<option value="23:30" selected={{ timeSelected "23:30" }}>23:30</option>
</select>
<div>
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<input type="checkbox" id="day_1" name="day_1" value="2" class="day_checkbox" checked="{{ dayChecked '2' }}" />
<label for="day_1">Ponedjeljak</label>
</div>
<div>
<input type="checkbox" id="day_2" name="day_2" value="3" class="day_checkbox" checked="{{ dayChecked '3' }}" />
<label for="day_2">Utorak</label>
</div>
<div>
<input type="checkbox" id="day_3" name="day_3" value="4" class="day_checkbox" checked="{{ dayChecked '4' }}" />
<label for="day_3">Srijeda</label>
</div>
<div>
<input type="checkbox" id="day_4" name="day_4" value="5" class="day_checkbox" checked="{{ dayChecked '5' }}" />
<label for="day_4">Četvrtak</label>
</div>
<div>
<input type="checkbox" id="day_5" name="day_5" value="6" class="day_checkbox" checked="{{ dayChecked '6' }}" />
<label for="day_5">Petak</label>
</div>
<div>
<input type="checkbox" id="day_6" name="day_6" value="7" class="day_checkbox" checked="{{ dayChecked '7' }}" />
<label for="day_6">Subota</label>
</div>
<div>
<input type="checkbox" id="day_7" name="day_7" value="1" class="day_checkbox" checked="{{ dayChecked '1' }}" />
<label for="day_7">Nedjelja</label>
</div>
<div class="modal-header">
<h4 class="modal-title">Automatsko zaljevanje</h4>
</div>
<div>
<button id="save_settings" name="save_settings">Zapamti</button>
<div class="modal-body">
<div class="form-group">
<select name="time_of_day" id="time_of_day" class="form-control">
<option value="00:00" selected={{ timeSelected "00:00" }}>00:00</option>
<option value="00:30" selected={{ timeSelected "00:30" }}>00:30</option>
<option value="01:00" selected={{ timeSelected "01:00" }}>01:00</option>
<option value="01:30" selected={{ timeSelected "01:30" }}>01:30</option>
<option value="02:00" selected={{ timeSelected "02:00" }}>02:00</option>
<option value="02:30" selected={{ timeSelected "02:30" }}>02:30</option>
<option value="03:00" selected={{ timeSelected "03:00" }}>03:00</option>
<option value="03:30" selected={{ timeSelected "03:30" }}>03:30</option>
<option value="04:00" selected={{ timeSelected "04:00" }}>04:00</option>
<option value="04:30" selected={{ timeSelected "04:30" }}>04:30</option>
<option value="05:00" selected={{ timeSelected "05:00" }}>05:00</option>
<option value="05:30" selected={{ timeSelected "05:30" }}>05:30</option>
<option value="06:00" selected={{ timeSelected "06:00" }}>06:00</option>
<option value="06:30" selected={{ timeSelected "06:30" }}>06:30</option>
<option value="07:00" selected={{ timeSelected "07:00" }}>07:00</option>
<option value="07:30" selected={{ timeSelected "07:30" }}>07:30</option>
<option value="08:00" selected={{ timeSelected "08:00" }}>08:00</option>
<option value="08:30" selected={{ timeSelected "08:30" }}>08:30</option>
<option value="09:00" selected={{ timeSelected "09:00" }}>09:00</option>
<option value="09:30" selected={{ timeSelected "09:30" }}>09:30</option>
<option value="10:00" selected={{ timeSelected "10:00" }}>10:00</option>
<option value="10:30" selected={{ timeSelected "10:30" }}>10:30</option>
<option value="11:00" selected={{ timeSelected "11:00" }}>11:00</option>
<option value="11:30" selected={{ timeSelected "11:30" }}>11:30</option>
<option value="12:00" selected={{ timeSelected "12:00" }}>12:00</option>
<option value="12:30" selected={{ timeSelected "12:30" }}>12:30</option>
<option value="13:00" selected={{ timeSelected "13:00" }}>13:00</option>
<option value="13:30" selected={{ timeSelected "13:30" }}>13:30</option>
<option value="14:00" selected={{ timeSelected "14:00" }}>14:00</option>
<option value="14:30" selected={{ timeSelected "14:30" }}>14:30</option>
<option value="15:00" selected={{ timeSelected "15:00" }}>15:00</option>
<option value="15:30" selected={{ timeSelected "15:30" }}>15:30</option>
<option value="16:00" selected={{ timeSelected "16:00" }}>16:00</option>
<option value="16:30" selected={{ timeSelected "16:30" }}>16:30</option>
<option value="17:00" selected={{ timeSelected "17:00" }}>17:00</option>
<option value="17:30" selected={{ timeSelected "17:30" }}>17:30</option>
<option value="18:00" selected={{ timeSelected "18:00" }}>18:00</option>
<option value="18:30" selected={{ timeSelected "18:30" }}>18:30</option>
<option value="19:00" selected={{ timeSelected "19:00" }}>19:00</option>
<option value="19:30" selected={{ timeSelected "19:30" }}>19:30</option>
<option value="20:00" selected={{ timeSelected "20:00" }}>20:00</option>
<option value="20:30" selected={{ timeSelected "20:30" }}>20:30</option>
<option value="21:00" selected={{ timeSelected "21:00" }}>21:00</option>
<option value="21:30" selected={{ timeSelected "21:30" }}>21:30</option>
<option value="22:00" selected={{ timeSelected "22:00" }}>22:00</option>
<option value="22:30" selected={{ timeSelected "22:30" }}>22:30</option>
<option value="23:00" selected={{ timeSelected "23:00" }}>23:00</option>
<option value="23:30" selected={{ timeSelected "23:30" }}>23:30</option>
</select>
</div>
<div class="form-group">
<div>
<input type="checkbox" id="day_1" name="day_1" value="2" class="day_checkbox form_control" checked="{{ dayChecked '2' }}" />
<label for="day_1">Ponedjeljak</label>
</div>
<div>
<input type="checkbox" id="day_2" name="day_2" value="3" class="day_checkbox form_control" checked="{{ dayChecked '3' }}" />
<label for="day_2">Utorak</label>
</div>
<div>
<input type="checkbox" id="day_3" name="day_3" value="4" class="day_checkbox form_control" checked="{{ dayChecked '4' }}" />
<label for="day_3">Srijeda</label>
</div>
<div>
<input type="checkbox" id="day_4" name="day_4" value="5" class="day_checkbox form_control" checked="{{ dayChecked '5' }}" />
<label for="day_4">Četvrtak</label>
</div>
<div>
<input type="checkbox" id="day_5" name="day_5" value="6" class="day_checkbox form_control" checked="{{ dayChecked '6' }}" />
<label for="day_5">Petak</label>
</div>
<div>
<input type="checkbox" id="day_6" name="day_6" value="7" class="day_checkbox form_control" checked="{{ dayChecked '7' }}" />
<label for="day_6">Subota</label>
</div>
<div>
<input type="checkbox" id="day_7" name="day_7" value="1" class="day_checkbox form_control" checked="{{ dayChecked '1' }}" />
<label for="day_7">Nedjelja</label>
</div>
</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>

View File

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

View File

@@ -1,17 +0,0 @@
var options = {
location: 40.7127+','+ 74.0059, // New York
unit: 'c',
success: function(weather) {
html = '<h2><i class="sw icon-'+weather.code+'"></i> '
html += weather.temp+'&deg;'+weather.units.temp+'</h2>';
html += '<ul><li>'+weather.city+', '+weather.region +'</li>';
html += '<li class="currently">'+weather.currently+'</li>';
$("#weather").html(html);
},
error: function(error) {
$("#weather").html('<p>'+error+'</p>');
}
}
Weather.options = options

8
app/client/startup.js Normal file
View File

@@ -0,0 +1,8 @@
Tracker.autorun(function () {
var id = Session.get('controller_id');
if (id) {
Meteor.subscribe("sensor_data", id);
var hamo = Meteor.subscribe("controller_state", id);
console.log(hamo);
}
});

View File

@@ -1,24 +1,19 @@
<template name="state">
<div class="col-md-3 col-md-offset-4">
<div>&nbsp;</div>
<div class="col-md-12">
{{#with controller_state}}
<h1>{{controller_id}}</h1>
<img src="{{ bucket_image }}" class="img-responsive center-block" id="bucket_image" /> {{#with last_sensor_reading}}
<div>
<strong>{{ temperatureValue }} °C, {{ humidityValue }} % </strong>
<img src="{{ bucket_image }}" class="img-responsive center-block" id="bucket_image" />
<div class="text-center">
{{controller_id}}:
{{#with last_sensor_reading}} <strong> temp: {{ temperatureValue }} °C, vlaga: {{ humidityValue }} % <br/>nivo vode: {{water_level}} </strong> {{/with}}
</div>
<div>
<div class="text-center">
Automatsko zalijevanje:<br /> <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>
{{/with}}
<div>Otpusni ventil: {{pretty_valve state.out_valve }}</div>
<div>Zadnja komunikacija: {{ last_communication_time }}</div>
{{/with}}
<div>
<button id="water_now" class="{{ water_now_button_class }}">Zalij sada</button>
<button id="stop_water_now" class="{{ stop_button_class }}">Prekini zalijevanje</button>
</div>
</div>
<div class="col-md-1">
</div>
</template>

View File

@@ -1,12 +1,6 @@
function controller_state() {
var controller = Session.get('controller_id');
var result = {}
if (controller) {
result = ControllerState.findOne({
controller_id: controller
});
}
var controllerId = Session.get('controller_id');
result = ControllerState.findOne({});
if (!result) {
result = {}
};
@@ -20,7 +14,8 @@ function sensor_data_collection() {
}, {
sort: {
created_at: -1
}
},
limit: 1
});
}
@@ -49,39 +44,76 @@ Template.state.helpers({
bucket_image: function() {
var sensor = last_sensor_reading();
if (sensor && sensor.tankFull === "1") {
return "/images/barell_full.png";
} else {
return "/images/barell_draining.png";
}
var stateObject = controller_state();
if (sensor) {
if (parseInt(sensor.tankFull) === 0 && stateObject.state.in_valve === 'open' && stateObject.state.out_valve === 'closed') return "/images/barrellFillingUp.png";
else if (parseInt(sensor.tankFull) === 1 && (stateObject.state.out_valve === 'closed')) return "/images/barrellFull.png";
else if (parseInt(sensor.tankFull) === 1 && (stateObject.state.out_valve === 'opening')) return "/images/barrellStartWateringFull.png";
else if (parseInt(sensor.tankFull) === 1 && (stateObject.state.out_valve === 'open')) return "/images/barrellWateringFull.png";
else if (parseInt(sensor.tankFull) === 1 && (stateObject.state.out_valve === 'closing')) return "/images/barrellStopWateringFull.png";
else if (parseInt(sensor.tankFull) === 0 && (stateObject.state.out_valve === 'closed')) return "/images/barrellNotFull.png";
else if (parseInt(sensor.tankFull) === 0 && (stateObject.state.out_valve === 'opening')) return "/images/barrellStartWateringNotFull.png";
else if (parseInt(sensor.tankFull) === 0 && (stateObject.state.out_valve === 'open')) return "/images/barrellWateringNotFull.png"
else if (parseInt(sensor.tankFull) === 0 && (stateObject.state.out_valve === 'closing')) return "/images/barrellStopWateringNotFull.png"
else return "/images/statusAmber.png";
} else return "/images/statusRed.png";
},
last_sensor_reading: last_sensor_reading,
last_communication_time: function() {
return moment(controller_state().time).fromNow();
},
water_level: function() {
var sensor = last_sensor_reading();
if (sensor) {
if (parseInt(sensor.tankFull) === 1) return '100 %';
else if (parseInt(sensor.tankLevel4) === 1) return '80 %';
else if (parseInt(sensor.tankLevel3) === 1) return '60 %';
else if (parseInt(sensor.tankLevel2) === 1) return '40 %';
else if (parseInt(sensor.tankLevel1) === 1) return '20 %';
else '0 %'
}
},
water_now_button_class: function() {
var stateObject = controller_state();
if (stateObject.state && (stateObject.state.out_valve === 'open' || stateObject.state.out_valve === 'opening')) {
return 'hidden';
return 'hidden btn btn-success';
} else {
return '';
return 'btn btn-success';
}
},
stop_button_class: function() {
var stateObject = controller_state();
if (stateObject.state && (stateObject.state.out_valve === 'closed' || stateObject.state.out_valve === 'closing')) {
return 'hidden';
return 'hidden btn btn-success';
} else {
return '';
return 'btn btn-success';
}
},
pretty_days: function(daysInNumbers) {
var days = ["Nedjelja", "Ponedjeljak", "Utorak", "Srijeda", "Četvrtak", "Petak", "Subota"];
if (!daysInNumbers || daysInNumbers.length == 0) {
return "Nikad"
}
else if (daysInNumbers.length == 7) {
return "Svaki dan"
} 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({
'click #water_now': function() {
var controller_id = Session.get('controller_id');
@@ -91,6 +123,14 @@ Template.state.events({
'click #stop_water_now': function() {
var controller_id = Session.get('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,48 @@
<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 class="row">
<div class="col-md-12 chart">
<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><strong>Zadnje zaljevanje: {{ last_out_valve_open }}</strong>
</div>
<div><strong>Zadnje punjenje: {{ last_in_valve_open }}</strong>
</div>
</div>
</div>
<div class="row hidden">
<div class="col-md-12 chart">
<h3>Temperatura:</h3>
<div id="temperature_graph">
</div>
</div>
</div>
<div class="row hidden">
<div class="col-md-12 chart">
<h3>Vlažnost:</h3>
<div id="humidity_graph">
</div>
</div>
</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>

159
app/client/state_details.js Normal file
View File

@@ -0,0 +1,159 @@
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();
},
last_out_valve_open: function() {
return moment(this.significantEvents.lastOutValveOpen).fromNow();
},
last_in_valve_open: function() {
return moment(this.significantEvents.lastInValveOpen).fromNow();
}
});
Template.state_details.events({});
Template.state_details.rendered = function() {
this.node = this.find('#temperature_graph'); // our d3 code goes here
var yScale = d3.scale.linear()
.domain([0, 4])
.range([10, 60]);
var xScale = d3.scale.linear()
.domain([0, self.duration])
.range([0, $(self.timelineWrapper).width()])
};
function sensor_data_collection() {
var controllerId = Session.get('controller_id');
return SensorData.find({
controllerId: controllerId
}, {
sort: {
created_at: 1
}
});
}
Template.state_details.rendered = function() {
var self = this;
/*
* add point when new temperature is added
*/
setTimeout(function() {
self.autorun(function() {
sensor_data_collection().observe({
added: function(reading) {
// buildTemperatureGraph();
// buildHumidityGraph()
}
});
});
}, 1000);
}
/*
* Function to draw the graph
*/
function buildTemperatureGraph() {
var unfilteredReadings = sensor_data_collection();
// we want to show only 11 points from all data
var breakingPoint = Math.floor(countValues(unfilteredReadings) / 10);
var sensorReadings = filterDataPoints(unfilteredReadings, breakingPoint);
var times = sensorReadings.map(function(reading) {
return moment(reading.created_at).format("HH:mm:ss");
});
var values = sensorReadings.map(function(reading) {
return reading.temperatureValue;
});
var breakingPoint = Math.floor(times.length / 11);
return new Chartist.Line('#temperature_graph', {
labels: times,
series: [values]
}, {
fullWidth: true,
chartPadding: {
right: 40
}
});
}
/*
* Function to draw the graph
*/
function buildHumidityGraph() {
var unfilteredReadings = sensor_data_collection();
// we want to show only 11 points from all data - filtering will add
// the last one so 10 + 1 = 11
var breakingPoint = Math.floor(countValues(unfilteredReadings) / 10);
var sensorReadings = filterDataPoints(unfilteredReadings, breakingPoint);
var times = sensorReadings.map(function(reading) {
return moment(reading.created_at).format("HH:mm:ss");
});
var values = sensorReadings.map(function(reading) {
return reading.humidityValue;
});
return new Chartist.Line('#humidity_graph', {
labels: times,
series: [values]
}, {
fullWidth: true,
chartPadding: {
right: 40
}
});
}
function filterDataPoints(data, breakingPoint) {
if (breakingPoint === 0) {
return data;
}
var result = [];
var index = 0;
var lastUnpushedRow = null;
data.forEach(function(row) {
if (index % breakingPoint === 0) {
result.push(row);
lastUnpushedRow = null;
} else {
lastUnpushedRow = row;
}
index++;
});
// in order to always have the latest value
if(lastUnpushedRow) {
result.push(lastUnpushedRow);
}
return result;
}
// dirty hack for the complicated way of getting
// acual number of values
function countValues(data) {
var count = 0;
data.forEach(function(row) {
count++;
});
return count;
};

View File

@@ -4,7 +4,6 @@
<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 '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>
</ul>

9
app/client/weather.html Normal file
View File

@@ -0,0 +1,9 @@
<template name="weather">
<div>&nbsp;</div>
<div class="col-md-12">
<div class="text-center">
{{>simpleWeather}}
</div>
</div>
</template>

38
app/client/weather.js Normal file
View File

@@ -0,0 +1,38 @@
var options = {
location: 44.0123 + ',' + 18.19455, // New Visoko
unit: 'c',
success: function(weather) {
html = "<h2><i class='sw icon-" + weather.code + "'></i>";
html += weather.temp + '&deg;' + weather.units.temp + '</h2>';
html += '<ul><li>' + weather.city + ', ' + weather.region + '</li>';
html += "<li class='currently'>" + weather.currently + '</li>';
html += '<hr />';
for (var i = 0; i < 5; i++) {
var forecast = weather.forecast[i];
html += '<div>';
html += "<h2><i class='sw icon-" + forecast.code + "'></i>";
html += forecast.high + '&deg;' + weather.units.temp + '</h2>';
html += "<li class='currently'> Najniža dnevna: "+ forecast.low + ' &deg; ' + weather.units.temp + "<br />" + daysInBosnian[forecast.day] + ': ' + forecast.text + '</li>';
html += "</div>";
}
$('#weather').html(html);
},
error: function(error) {
$('#weather').html('<p>' + error + '</p>');
}
}
var daysInBosnian = {
'Sun': 'Nedjelja',
'Mon': 'Ponedjeljak',
'Tue': 'Utorak',
'Wed': 'Srijeda',
'Thu': 'Četvrtak',
'Fri': 'Petak',
'Sat': 'Subota'
};
Weather.options = options

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: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 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,11 +9,18 @@ Api.addRoute('sensorData', {
authRequired: false
}, {
post: function() {
console.log("Body params", this.bodyParams);
reactToSensorData(this.bodyParams);
SensorData.insert({
temperatureValue: parseFloat(this.bodyParams.temperatureValue),
humidityValue: parseFloat(this.bodyParams.humidityValue),
tankLevel0: this.bodyParams.tankLevel0,
tankLevel1: this.bodyParams.tankLevel1,
tankLevel2: this.bodyParams.tankLevel2,
tankLevel3: this.bodyParams.tankLevel3,
tankLevel4: this.bodyParams.tankLevel4,
tankFull: this.bodyParams.tankFull,
startPumpingAt: this.bodyParams.startPumpingAt,
stopPumpingAt: this.bodyParams.stopPumpingAt,
owner: this.bodyParams.owner,
controllerId: this.bodyParams.controllerId,
created_at: new Date()
@@ -23,16 +30,67 @@ Api.addRoute('sensorData', {
});
function reactToSensorData(nextSensorReading) {
console.log("reacting to sensor");
var controllerId = nextSensorReading.controllerId;
var state = stateOrDefault(controllerId).state;
var startPumpingLevelReached;
if (nextSensorReading.startPumpingAt == 'TANKLEVEL0') startPumpingLevelReached = (parseInt(nextSensorReading.tankLevel0) === 0)
else if (nextSensorReading.startPumpingAt == 'TANKLEVEL1') startPumpingLevelReached = (parseInt(nextSensorReading.tankLevel1) === 0)
else if (nextSensorReading.startPumpingAt == 'TANKLEVEL2') startPumpingLevelReached = (parseInt(nextSensorReading.tankLevel2) === 0)
else if (nextSensorReading.startPumpingAt == 'TANKLEVEL3') startPumpingLevelReached = (parseInt(nextSensorReading.tankLevel3) === 0)
else if (nextSensorReading.startPumpingAt == 'TANKLEVEL4') startPumpingLevelReached = (parseInt(nextSensorReading.tankLevel4) === 0)
else startPumpingLevelReached = true;
var shouldStartPumping = (!state.in_valve || state.in_valve === 'closed') && ((parseInt(nextSensorReading.tankFull) === 0) && startPumpingLevelReached && (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 stopPumpingLevelReached;
if (nextSensorReading.stopPumpingAt == 'TANKLEVEL0') stopPumpingLevelReached = (parseInt(nextSensorReading.tankLevel0) === 1)
else if (nextSensorReading.stopPumpingAt == 'TANKLEVEL1') stopPumpingLevelReached = (parseInt(nextSensorReading.tankLevel1) === 1)
else if (nextSensorReading.stopPumpingAt == 'TANKLEVEL2') stopPumpingLevelReached = (parseInt(nextSensorReading.tankLevel2) === 1)
else if (nextSensorReading.stopPumpingAt == 'TANKLEVEL3') stopPumpingLevelReached = (parseInt(nextSensorReading.tankLevel3) === 1)
else if (nextSensorReading.stopPumpingAt == 'TANKLEVEL4') stopPumpingLevelReached = (parseInt(nextSensorReading.tankLevel4) === 1)
else stopPumpingLevelReached = false;
var shouldStopPumping = (state.in_valve === 'open' || state.in_valve === 'opening') && (parseInt(nextSensorReading.tankFull) === 1 || stopPumpingLevelReached || 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', {
authRequired: false
}, {
post: function() {
console.log("Body params", this.bodyParams);
console.log("setting state", this.bodyParams);
return ControllerState.update({
controller_id: this.urlParams.id
}, {
'$set': {
'state.out_valve': this.bodyParams.out_valve,
'state.in_valve': this.bodyParams.in_valve,
'time': new Date(),
'set_by': 'client'
}
@@ -43,6 +101,7 @@ Api.addRoute('state/:id', {
}
});
function stateOrDefault(id) {
var stateEntry = ControllerState.findOne({
controller_id: id,
@@ -52,13 +111,13 @@ function stateOrDefault(id) {
stateEntry = ControllerState.insert({
controller_id: id,
state: {
out_valve: 'closed'
out_valve: 'closed',
in_valve: 'closed'
},
time: new Date(),
config: {
draining_period_amount: 5,
draining_period_amount: 40,
draining_period_unit: 'minutes'
},
set_by: 'server'
});

View File

@@ -21,6 +21,15 @@ function setOutValveTo(controller_id, nextState) {
'set_by': 'server'
}
});
if(nextState === "open") {
ControllerState.update(state._id, {
'$set': {
'significantEvents.lastOutValveOpen': new Date(),
}
});
}
}
function openOutValve(controller_id) {
@@ -40,7 +49,9 @@ function openOutValve(controller_id) {
closeOutValve(controller_id);
}
});
console.log("Finished adding cron ", controller_id);
console.log(Meteor.sharedFunctions);
Meteor.sharedFunctions.reactToSensorData(last_sensor_reading(controller_id));
}
function closeOutValve(controller_id) {
@@ -51,6 +62,10 @@ function closeOutValve(controller_id) {
SyncedCron.remove(jobName);
setOutValveTo(controller_id, 'closing');
console.log("Finished clearing cron ", controller_id);
console.log(Meteor.sharedFunctions);
Meteor.sharedFunctions.reactToSensorData(last_sensor_reading(controller_id));
}
function clearLog() {
@@ -88,6 +103,28 @@ function saveControllerConfig(controller_id, time, days) {
});
}
function last_sensor_reading(controller_id) {
var result = null;
if (controller_id) {
result = SensorData.find({
controllerId: controller_id
}, {
sort: {
created_at: -1
},
limit: 1
});
}
if (result && result.count() > 0) {
return result.fetch()[0];
} else {
return {}
}
}
Meteor.methods({
openOutValve: openOutValve,
closeOutValve: closeOutValve,

View File

@@ -0,0 +1,18 @@
// This code only runs on the server
Meteor.publish("sensor_data", function(controllerId) {
return SensorData.find({
controllerId: controllerId
}, {
sort: {
created_at: -1
},
limit: 100
});
});
// This code only runs on the server
Meteor.publish("controller_state", function(controllerId) {
return ControllerState.find({
controller_id: controllerId
});
});

View File

@@ -18,7 +18,14 @@ if (Meteor.isServer) {
SensorData.insert({
temperatureValue: parseFloat(this.bodyParams.temperatureValue),
humidityValue: parseFloat(this.bodyParams.humidityValue),
tankFull: this.bodyParams.tankFull,
tankLevel0: this.bodyParams.tankLevel0,
tankLevel1: this.bodyParams.tankLevel1,
tankLevel2: this.bodyParams.tankLevel2,
tankLevel3: this.bodyParams.tankLevel3,
tankLevel4: this.bodyParams.tankLevel4,
tankFull: this.bodyParams.tankFull,
startPumpingAt: this.bodyParams.startPumpingAt,
stopPumpingAt: this.bodyParams.stopPumpingAt,
owner: this.bodyParams.owner,
created_at: new Date()
});

View File

@@ -3,12 +3,19 @@
## Installation
1. Go to every subdirectory in drivers directory and follow instructions about installation of drivers
2. edit controller/config/__init__.py and set your controller ID to unique number
2. edit controller/config/__init__.py and set your controller ID to unique number
3. configure cron to run controller.py every 15 minutes as a superuser:
```
crontab -e -u root
#enter these lines
*/15 * * * * /usr/bin/python /home/pi/projects/tfm/controller/sensors.py "Automatski, Senad Uka" 120
*/1 * * * * /usr/bin/python /home/pi/projects/tfm/controller/sync_state.py "Automatski, Senad Uka" 120
*/5 * * * * cd /home/pi/projects/tfm/controller && sh activity.sh
*/30 * * * * /usr/bin/python /home/pi/projects/tfm/controller/network_check.py
```
4. add following lines at the end of /etc/rc.local
```
python /home/pi/projects/tfm/controller/lockdown.py
python /home/pi/projects/tfm/controller/dweet.py
```

3
controller/activity.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
sudo /usr/bin/python /home/pi/projects/tfm/controller/sensors.py
sudo /usr/bin/python /home/pi/projects/tfm/controller/sync_state.py

View File

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

View File

@@ -0,0 +1,17 @@
GPIO_PIN_DHT = 4 # BCM
SENSORDATA_URL = 'http://agrar.zoblak.com/api/v1.0/sensorData'
GPIO_PIN_TANKLEVEL0 = 5 # BCM
GPIO_PIN_TANKLEVEL1 = 6 # BCM
GPIO_PIN_TANKLEVEL2 = 13 # BCM
GPIO_PIN_TANKLEVEL3 = 19 # BCM
GPIO_PIN_TANKLEVEL4 = 26 # BCM
GPIO_PIN_TANKFULL = 20 # BCM
GPIO_PIN_OUT_VALVE = 21 # BCM
GPIO_PIN_IN_VALVE = 18 # BCM
START_PUMPING_AT = 'TANKFULL' # start pumping when this level = 0
STOP_PUMPING_AT = 'TANKFULL' # stop pumping when this level = 1
API_BASE_URL = 'http://agrar.zoblak.com/api/v1.0'
CONTROLLER_ID = '120' # every controller must have a different one
STATE_FILE = '/var/run/controller_state'

6
controller/dweet.py Normal file
View File

@@ -0,0 +1,6 @@
import config
import commands
command = """curl -H "Content-Type: application/json" -X POST -d '{"message": "I am aliveeee (again!)", "controller_id": %s}' https://dweet.io:443/dweet/quietly/for/5410ab1e-319c-4f14-a2e4-04725df69121""" % config.CONTROLLER_ID
print commands.getoutput(command)

3
controller/lockdown.py Normal file
View File

@@ -0,0 +1,3 @@
import state
state.safely_panic()

View File

@@ -0,0 +1,3 @@
import state
state.reboot_if_network_down()

View File

@@ -5,17 +5,38 @@ import Adafruit_DHT
import config
import RPi.GPIO as GPIO
# Try to read the state of GPIO_PIN_TANKFULL
# Try to read the state of GPIO_PIN_TANKLEVELx and GPIO_PIN_TANKFULL
GPIO.setmode(GPIO.BCM)
GPIO.setup(config.GPIO_PIN_TANKLEVEL0, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(config.GPIO_PIN_TANKLEVEL1, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(config.GPIO_PIN_TANKLEVEL2, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(config.GPIO_PIN_TANKLEVEL3, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(config.GPIO_PIN_TANKLEVEL4, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(config.GPIO_PIN_TANKFULL, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
tankFull = GPIO.input(config.GPIO_PIN_TANKFULL)
# tank sensor has inverse logic - 0 when it is full 1 when it is not
# so we are inverting its value here
tankLevel0 = (GPIO.input(config.GPIO_PIN_TANKLEVEL0) == GPIO.LOW)
tankLevel1 = (GPIO.input(config.GPIO_PIN_TANKLEVEL1) == GPIO.LOW)
tankLevel2 = (GPIO.input(config.GPIO_PIN_TANKLEVEL2) == GPIO.LOW)
tankLevel3 = (GPIO.input(config.GPIO_PIN_TANKLEVEL3) == GPIO.LOW)
tankLevel4 = (GPIO.input(config.GPIO_PIN_TANKLEVEL4) == GPIO.LOW)
tankFull = (GPIO.input(config.GPIO_PIN_TANKFULL) == GPIO.LOW)
GPIO.cleanup()
print 'Bacva Level0: {}'.format(tankLevel0)
print 'Bacva Level1: {}'.format(tankLevel1)
print 'Bacva Level2: {}'.format(tankLevel2)
print 'Bacva Level3: {}'.format(tankLevel3)
print 'Bacva Level4: {}'.format(tankLevel4)
print 'Bacva puna: {}'.format(tankFull)
# Go on to DHT
SENSOR_TYPE = Adafruit_DHT.DHT11
controller_id = config.CONTROLLER_ID
owner = "Controller: %s" % controller_id
startPumpingAt = config.START_PUMPING_AT
stopPumpingAt = config.STOP_PUMPING_AT
# Try to grab a sensor reading. Use the read_retry method which will retry up
# to 15 times to get a sensor reading (waiting 2 seconds between each retry).
@@ -28,9 +49,9 @@ humidity, temperature = Adafruit_DHT.read_retry(SENSOR_TYPE, config.GPIO_PIN_DHT
# the results will be null (because Linux can't
# guarantee the timing of calls to read the sensor).
# If this happens try again!
if temperature is not None and humidity is not None:
response = requests.post(config.SENSORDATA_URL, json={"owner": owner, "temperatureValue": temperature, "humidityValue":humidity, "tankFull":tankFull,
"controllerId": controller_id
if tankFull is not None:
response = requests.post(config.SENSORDATA_URL, json={"owner": owner, "temperatureValue": temperature, "humidityValue":humidity, "tankLevel0": "1" if tankLevel0 else "0","tankLevel1": "1" if tankLevel1 else "0","tankLevel2": "1" if tankLevel2 else "0","tankLevel3": "1" if tankLevel3 else "0", "tankLevel4": "1" if tankLevel4 else "0", "tankFull": "1" if tankFull else "0",
"startPumpingAt": startPumpingAt,"stopPumpingAt": stopPumpingAt,"controllerId": controller_id
})
print 'Temp={0:0.1f}*C'.format(temperature)
print 'Humidity={0:0.1f}%'.format(humidity)

View File

@@ -2,26 +2,42 @@ import config
from state.server import Server
from state.changer import Changer
from state.file import File
import commands
def safely_panic():
safe_state = {}
changer = Changer(safe_state,safe_state)
changer.stop_everything()
def reboot_if_network_down():
try:
server = Server(config.API_BASE_URL, config.CONTROLLER_ID)
server_state = server.get_state()
print "Got state from server: " + repr(server_state)
except:
print "Problem with the network!"
commands.getoutput('/sbin/shutdown -r +3')
def sync():
server = Server(config.API_BASE_URL, config.CONTROLLER_ID)
local = File(config.STATE_FILE)
server_state = server.get_state()
try:
server = Server(config.API_BASE_URL, config.CONTROLLER_ID)
local = File(config.STATE_FILE)
server_state = server.get_state()
if local.present():
local.load()
print "local present: " + repr(local.data)
else:
local.data = server_state
print "local not present, server: " + repr(local.data)
local.save()
local_state = local.data
if local.present():
local.load()
print "local present: " + repr(local.data)
else:
local.data = server_state
print "local not present, server: " + repr(local.data)
local.save()
changer = Changer(local_state, server_state)
current_state = changer.process_change()
local_state = local.data
changer = Changer(local_state, server_state)
current_state = changer.process_change()
server.post_state(current_state)
server.post_state(current_state)
except:
print " panicking safely ! "
safely_panic()

View File

@@ -7,29 +7,60 @@ class Changer(object):
self.local_state = local_state
self.remote_state = remote_state
GPIO.setmode(GPIO.BCM) # Broadcom pin-numbering scheme
GPIO.setup(config.GPIO_PIN_VALVE, GPIO.OUT)
self.states = {
'opening': self.open_valve,
'closing': self.close_valve,
'open': self.open_valve,
'closed': self.close_valve
GPIO.setup(config.GPIO_PIN_OUT_VALVE, GPIO.OUT)
GPIO.setup(config.GPIO_PIN_IN_VALVE, GPIO.OUT)
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 stop_everything(self):
self.close_in_valve()
self.close_out_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):
self.validate_states()
change = self.states.get(self.remote_state['out_valve'], None )
if change is not None:
change()
out_valve_change = self.out_valve_states.get(self.safe_remote_state('out_valve'), None )
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
def open_valve(self):
GPIO.output(config.GPIO_PIN_VALVE, GPIO.HIGH)
def open_in_valve(self):
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'
def close_valve(self):
GPIO.output(config.GPIO_PIN_VALVE, GPIO.LOW)
def close_out_valve(self):
GPIO.output(config.GPIO_PIN_OUT_VALVE, GPIO.LOW)
self.local_state['out_valve'] = 'closed'
def validate_states(self):

View File

@@ -0,0 +1,33 @@
import json
import requests
DWEET_UID = "5410ab1e-319c-4f14-a2e4-04725df69121"
class DweetServer(object):
"Gets state from server and sends it to the server after change"
def __init__(self, controller_id=None):
self.url_base = "https://dweet.io:443/dweet/quietly/for/%s" % DWEET_UID
self.controller_id = controller_id
def send_message(self, message):
result = requests.post(self.full_url(),
json=json.dumps({
'controller_id': self.controller_id,
'message': message
}))
return handle_response(result)
def full_url(self):
if self.controller_id is None:
raise ClassNotReadyException("Controller id not set!")
if self.url_base is None:
raise ClassNotReadyException("URL base not set!")
return self.url_base
def handle_response(response):
if response.status_code != 200 or response.status_code != 204:
raise Exception("Response not 200 or 204!")
else:
return response.json()

View File

@@ -16,8 +16,6 @@ class Server(object):
result = requests.post(self.full_url('state/%s') % self.controller_id, local_state)
return handle_response(result)
def full_url(self, action):
if self.controller_id is None:
raise ClassNotReadyException("Controller id not set!")

136
misc/calculator.html Normal file
View File

@@ -0,0 +1,136 @@
<html>
<head>
<meta charset="UTF-8">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
</head>
<body>
<div class="jumbotron">
<div class="lead text-center">
<img src="./zoblak.png" width="30%" />
<br /> Zoblak Agrar Plus se isplati za <span id="vrijemeZaBreakEven" class="bg-success"></span> mjeseci. U periodu od <span id="brojSezona"></span> godine ostvaruje uštedu u novcu od <span id="iznosUstede" class="bg-success"></span> KM, te uštedu u vremenu od <span id="satiUProjekciji" class="bg-success"></span> sati za podešeni slučaj.
</div>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-6">
<p>Na troškove goriva mjesečno trošite <span id="iznosGoriva"></span> KM. Na rad potreban za zalijevanje trošite mjesečno <span id="satiMjesecno"></span> sati što uz zadanu satnicu iznosi <span id="plata"></span> KM. Zajedno to je <span id="ukupnoTroskovaMjesecno"></span> KM troška na zalijevanje. </p>
<p>Okvirna cijena Zoblak Agrar Plus iznosi <span id="bazaCijeneZoblaka"></span> KM + sistem navodnjavanja</p>
<p class="bg-primary">NAPOMENA: finalna cijena može biti niža ili viša i poznata je nakon izrade projekta).</p>
</div>
<div class="col-md-6 col-sm-6">
<form method="post">
<div class="form-group ">
<label class="control-label " for="number">
Vaša udaljenost od polja u km
</label>
<input class="form-control ulazni-parametar" id="pudaljenost" type="number" min="1" max="200" step="0.1" value="5" />
</div>
<div class="form-group ">
<label class="control-label " for="number1">
Prosjecna potro&scaron;nja automobila na 100 km
</label>
<input class="form-control ulazni-parametar" id="ppotrosnja" type="number" min="2" max="15" value="7" step="0.1" />
</div>
<div class="form-group ">
<label class="control-label " for="select">
Koliko često treba zaljevati kulturu?
</label>
<select class="select form-control" id="pfrekvencija_zalijevanja">
<option value="1">
Svaki dan
</option>
<option value="2">
Jednom u dva dana
</option>
<option value="3">
Jednom u tri dana
</option>
<option value="4">
Jednom u četiri dana
</option>
</select>
</div>
<div class="form-group ">
<label class="control-label " for="number2">
Koliko vremena traje put prema polju (u oba smijera) i jedan ciklus zalijevanja? U satima.
</label>
<input class="form-control ulazni-parametar" id="pvrijeme" type="number" min="2" max="10" value="1.5" step="0.5" />
</div>
<div class="form-group ">
<label class="control-label " for="number3">
Satnica osobe koja zalijeva u KM
</label>
<input class="form-control ulazni-parametar" id="psatnica" type="number" min="1" max="100" value="20" step="1" />
</div>
<div class="form-group ">
<label class="control-label " for="number3">
Trajanje sezone u mjesecima
</label>
<input class="form-control ulazni-parametar" id="psezona" type="number" min="1" max="12" value="4" step="1" />
</div>
<div class="form-group ">
<label class="control-label " for="number3">
Na koliko godina/sezona se kalkulacija vrši
</label>
<input class="form-control ulazni-parametar" id="pbroj_sezona" type="number" min="1" max="10" value="3" step="1" />
</div>
</form>
</div>
</div>
</div>
<script type="text/javascript">
function iskalkulisi() {
var udaljenost = parseFloat($('#pudaljenost').val()) || 10;
var potrosnja = parseFloat($('#ppotrosnja').val()) || 7;
var frekvencija = parseInt($('#pfrekvencija_zalijevanja').val()) || 2;
var vrijeme = parseFloat($('#pvrijeme').val()) || 1.5;
var satnica = parseFloat($('#psatnica').val()) || 20;
var sezona = parseInt($('#psezona').val()) || 4;
var brojSezona = parseInt($('#pbroj_sezona').val()) || 3;
var bazaCijeneZoblaka = 1000;
var bazaCijeneVentila = 0;
var bazaCijeneGoriva = 1.70;
var godisnjeOdrzavanjeZoblaka = 200;
// rezultati
var zalijevanjaMjesecno = Math.ceil(30 / frekvencija);
var satiMjesecno = Math.ceil(vrijeme) * zalijevanjaMjesecno;
var plata = satiMjesecno * satnica;
var iznosGoriva = Math.ceil((potrosnja / 100) * bazaCijeneGoriva * (udaljenost * 2) * zalijevanjaMjesecno);
var ukupnoTroskovaMjesecno = iznosGoriva + plata;
var trosakZoblaka = bazaCijeneZoblaka + godisnjeOdrzavanjeZoblaka * (brojSezona - 1);
var satiUProjekciji = satiMjesecno * sezona * brojSezona;
var novcaUProjekciji = ukupnoTroskovaMjesecno * sezona * brojSezona;
var iznosUstede = novcaUProjekciji - trosakZoblaka
var vrijemeZaBreakEven = Math.ceil(bazaCijeneZoblaka / ukupnoTroskovaMjesecno);
$('#vrijemeZaBreakEven').html(vrijemeZaBreakEven);
$('#brojSezona').html(brojSezona);
$('#iznosUstede').html(iznosUstede);
$('#satiUProjekciji').html(satiUProjekciji);
$('#iznosGoriva').html(iznosGoriva);
$('#satiMjesecno').html(satiMjesecno);
$('#plata').html(plata);
$('#ukupnoTroskovaMjesecno').html(ukupnoTroskovaMjesecno);
$('#bazaCijeneZoblaka').html(bazaCijeneZoblaka);
};
$(document).ready(function() {
iskalkulisi();
$(".ulazni-parametar").keyup(iskalkulisi);
$(".ulazni-parametar").change(iskalkulisi);
});
</script>
</body>
</html>

43
misc/latest_dweets.html Normal file
View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>
<style>
body {
background: #F9F9FA;
}
</style>
</head>
<body>
<a href="#" id="get-data">Get JSON data</a>
<div id="show-data"></div>
<script>
$(document).ready(function() {
$('#get-data').click(function() {
var showData = $('#show-data');
$.getJSON('https://dweet.io:443/get/dweets/for/5410ab1e-319c-4f14-a2e4-04725df69121', function(data) {
console.log(data);
var items = data.with.map(function(item) {
return item.created + ': ' + item.content.controller_id + ' (' + item.content.message + ')';
});
showData.empty();
if (items.length) {
var content = '<li>' + items.join('</li><li>') + '</li>';
var list = $('<ul />').html(content);
showData.append(list);
}
});
showData.text('Loading the JSON file.');
});
});
</script>
</body>
</html>

24
misc/latest_dweets.js Normal file
View File

@@ -0,0 +1,24 @@
$(document).ready(function () {
$('#get-data').click(function () {
var showData = $('#show-data');
$.getJSON('https://dweet.io:443/get/dweets/for/5410ab1e-319c-4f14-a2e4-04725df69121', function (data) {
console.log(data);
var items = data.items.map(function (item) {
return item.key + ': ' + item.value;
});
showData.empty();
if (items.length) {
var content = '<li>' + items.join('</li><li>') + '</li>';
var list = $('<ul />').html(content);
showData.append(list);
}
});
showData.text('Loading the JSON file.');
});
});

BIN
misc/zoblak.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB