Zadnje zaljevanje: {{ last_out_valve_open }}
diff --git a/app/client/state_details.js b/app/client/state_details.js
index 17b3e26..35706ba 100644
--- a/app/client/state_details.js
+++ b/app/client/state_details.js
@@ -9,10 +9,13 @@ Template.state_details.helpers({
return moment(this.time).fromNow();
},
last_out_valve_open: function() {
- return moment(this.significantEvents.lastOutValveOpen).fromNow();
+ return "";
},
last_in_valve_open: function() {
- return moment(this.significantEvents.lastInValveOpen).fromNow();
+ return "";
+ },
+ picture_requested: function(state) {
+ return (state.picture_requested === 'true') ? 'DA' : 'NE';
}
});
diff --git a/app/client/surveillance.html b/app/client/surveillance.html
new file mode 100644
index 0000000..2750fa9
--- /dev/null
+++ b/app/client/surveillance.html
@@ -0,0 +1,15 @@
+
+
+
+
+ Fotografisano: {{ picture_time }}
+
+
+
+
+
+
+

+
+
+
diff --git a/app/client/surveillance.js b/app/client/surveillance.js
new file mode 100644
index 0000000..d61bd35
--- /dev/null
+++ b/app/client/surveillance.js
@@ -0,0 +1,49 @@
+function controller_state() {
+ var controllerId = Session.get('controller_id');
+ result = ControllerState.findOne({});
+ if (!result) {
+ result = {}
+ };
+ return result;
+};
+
+function picture() {
+ var controllerId = Session.get('controller_id');
+ result = Picture.findOne({
+ controller_id: controllerId
+ });
+ console.log("rez je", result);
+ if (!result) {
+ result = {}
+ };
+ return result;
+};
+
+
+
+Template.surveillance.helpers({
+ controller_state: controller_state,
+ picture_src: function() {
+ var picture_base64 = picture().picture_base64;
+ var picture_src = '/images/noImage.png';
+ if (picture_base64) {
+ picture_src = 'data:image/jpeg;charset=utf-8;base64,' + picture_base64;
+ }
+ return picture_src;
+ },
+ picture_time: function() {
+ var picture_entry = picture();
+ if (picture_entry) {
+ return moment(picture_entry.time).fromNow();
+ } else {
+ return "Nikad!";
+ }
+ }
+});
+
+Template.surveillance.events({
+ 'click #request_new_picture': function() {
+ var controller_id = Session.get('controller_id');
+ Meteor.call('requestNewPicture', controller_id)
+ }
+});
diff --git a/app/client/tabs.html b/app/client/tabs.html
index 036106b..accaac7 100644
--- a/app/client/tabs.html
+++ b/app/client/tabs.html
@@ -4,8 +4,8 @@
Stanje
Vrijeme
Novosti
+
Videonadzor
-
diff --git a/app/client/tabs.js b/app/client/tabs.js
index 0de0d4b..ff1bd86 100644
--- a/app/client/tabs.js
+++ b/app/client/tabs.js
@@ -26,10 +26,12 @@ Template.tabs.events({
'click .log': function() {
Session.set('templateName', 'log');
},
+ 'click .surveillance': function() {
+ Session.set('templateName', 'surveillance');
+ },
'click .settings': function() {
Session.set('templateName', 'settings');
},
-
'click #switch': function() {
var instance = Template.instance();
controller_id = instance.$('#controller').val();
diff --git a/app/common/collections.js b/app/common/collections.js
index c45a563..84c7465 100644
--- a/app/common/collections.js
+++ b/app/common/collections.js
@@ -1,3 +1,4 @@
SensorData = new Mongo.Collection("sensorData");
ControllerState = new Mongo.Collection("controller_states");
+Picture = new Mongo.Collection("pictures");
diff --git a/app/public/images/noImage.png b/app/public/images/noImage.png
new file mode 100644
index 0000000..757a9bf
Binary files /dev/null and b/app/public/images/noImage.png differ
diff --git a/app/server/api.js b/app/server/api.js
index 7f654d9..ed6b715 100644
--- a/app/server/api.js
+++ b/app/server/api.js
@@ -10,7 +10,7 @@ Api.addRoute('sensorData', {
}, {
post: function() {
reactToSensorData(this.bodyParams);
- SensorData.insert({
+ var sensorObject = {
temperatureValue: parseFloat(this.bodyParams.temperatureValue),
humidityValue: parseFloat(this.bodyParams.humidityValue),
tankLevel0: this.bodyParams.tankLevel0,
@@ -24,7 +24,8 @@ Api.addRoute('sensorData', {
owner: this.bodyParams.owner,
controllerId: this.bodyParams.controllerId,
created_at: new Date()
- });
+ };
+ SensorData.insert(sensorObject);
return [];
}
});
@@ -47,11 +48,11 @@ reactToSensorData = function(nextSensorReading) {
var shouldStartAutomaticPumping = (!state.in_valve || state.in_valve === 'closed') && ((parseInt(nextSensorReading.tankFull) === 0) && startPumpingLevelReached && (state.out_valve === 'closed' || state.out_valve === 'closing'));
- console.log("State: ", state);
+ console.log("State: ", state);
var shouldStartPumping = shouldStartAutomaticPumping && config && !config.manualInflow;
- console.log("shouldStartAutomaticPumping: ", shouldStartAutomaticPumping);
+ console.log("shouldStartAutomaticPumping: ", shouldStartAutomaticPumping);
console.log("shouldStartPumping: ", shouldStartPumping);
@@ -75,11 +76,11 @@ reactToSensorData = function(nextSensorReading) {
else if (nextSensorReading.stopPumpingAt == 'TANKLEVEL4') stopPumpingLevelReached = (parseInt(nextSensorReading.tankLevel4) === 1)
else stopPumpingLevelReached = false;
- var shouldStopAutomaticPumping = (state.in_valve === 'open' || state.in_valve === 'opening') && (parseInt(nextSensorReading.tankFull) === 1 || stopPumpingLevelReached || state.out_valve === 'open' || state.out_valve === 'opening');
+ var shouldStopAutomaticPumping = (state.in_valve === 'open' || state.in_valve === 'opening') && (parseInt(nextSensorReading.tankFull) === 1 || stopPumpingLevelReached || state.out_valve === 'open' || state.out_valve === 'opening');
- var shouldStopPumping = shouldStopAutomaticPumping && config && !config.manualInflow;
+ var shouldStopPumping = shouldStopAutomaticPumping && config && !config.manualInflow;
- console.log("shouldStopPumping: ", shouldStopPumping);
+ console.log("shouldStopPumping: ", shouldStopPumping);
if (shouldStopPumping) {
@@ -106,6 +107,7 @@ Api.addRoute('state/:id', {
'$set': {
'state.out_valve': this.bodyParams.out_valve,
'state.in_valve': this.bodyParams.in_valve,
+ 'state.picture_requested': this.bodyParams.picture_requested,
'time': new Date(),
'set_by': 'client'
}
@@ -116,10 +118,35 @@ Api.addRoute('state/:id', {
}
});
+Api.addRoute('picture/:id', {
+ authRequired: false
+}, {
+ post: function() {
+ console.log("setting picture", this.bodyParams);
+ return Picture.upsert({
+ controller_id: this.urlParams.id
+ }, {
+ '$set': {
+ 'picture_base64': this.bodyParams.picture_base64,
+ 'time': new Date()
+ }
+ });
+ }
+});
+
+function base64_to_buffer(base64string) {
+ var buf;
+ if (typeof Buffer.from === "function") {
+ buf = Buffer.from(base64string, 'base64');
+ } else {
+ buf = new Buffer(base64string, 'base64');
+ }
+ return buf;
+}
function stateOrDefault(id) {
var stateEntry = ControllerState.findOne({
- controller_id: id,
+ controller_id: id
});
if (stateEntry === undefined) {
diff --git a/app/server/methods.js b/app/server/methods.js
index 16d71cb..4459f77 100644
--- a/app/server/methods.js
+++ b/app/server/methods.js
@@ -52,6 +52,17 @@ function setInValveTo(controller_id, nextState) {
}
+function requestNewPicture(controller_id) {
+ var state = controller_state(controller_id);
+ ControllerState.update(state._id, {
+ '$set': {
+ 'state.picture_requested': 'true',
+ 'time': new Date(),
+ 'set_by': 'server'
+ }
+ });
+};
+
function openInValve(controller_id) {
var state = controller_state(controller_id);
var config = state.config;
@@ -174,5 +185,6 @@ Meteor.methods({
openInValve: openInValve,
closeInValve: closeInValve,
clearLog: clearLog,
- saveControllerConfig: saveControllerConfig
+ saveControllerConfig: saveControllerConfig,
+ requestNewPicture: requestNewPicture
});
diff --git a/app/server/publications.js b/app/server/publications.js
index cd14e21..30ae624 100644
--- a/app/server/publications.js
+++ b/app/server/publications.js
@@ -16,3 +16,11 @@ Meteor.publish("controller_state", function(controllerId) {
controller_id: controllerId
});
});
+
+
+// This code only runs on the server
+Meteor.publish("pictures", function(controllerId) {
+ return Picture.find({
+ controller_id: controllerId
+ });
+});
diff --git a/controller/README.md b/controller/README.md
index 78dbd22..64f17b9 100644
--- a/controller/README.md
+++ b/controller/README.md
@@ -11,6 +11,8 @@ crontab -e -u root
#enter these lines
*/5 * * * * cd /home/pi/projects/tfm/controller && sh activity.sh
*/30 * * * * /usr/bin/python /home/pi/projects/tfm/controller/network_check.py
+*/1 * * * * /usr/bin/python /home/pi/projects/tfm/controller/camera_capture.py
+*/1 * * * * /usr/bin/python /home/pi/projects/tfm/controller/camera_send.py
```
4. add following lines at the end of /etc/rc.local
@@ -19,3 +21,20 @@ crontab -e -u root
python /home/pi/projects/tfm/controller/lockdown.py
python /home/pi/projects/tfm/controller/dweet.py
```
+
+5. create directory
+```
+mkdir -p /mnt/zoblakdata
+```
+
+6. add following line to /etc/fstab for ramdisk
+```
+tmpfs /mnt/zoblakdata tmpfs nodev,nosuid,size=100M 0 0
+```
+
+7. install packages
+
+```
+sudo apt-get update
+sudo apt-get install libav-tools
+```
diff --git a/controller/camera_capture.py b/controller/camera_capture.py
new file mode 100644
index 0000000..ff590d5
--- /dev/null
+++ b/controller/camera_capture.py
@@ -0,0 +1,3 @@
+import drivers.camera as camera
+
+camera.capture_picture()
diff --git a/controller/camera_send.py b/controller/camera_send.py
new file mode 100644
index 0000000..afc0b9a
--- /dev/null
+++ b/controller/camera_send.py
@@ -0,0 +1,16 @@
+import drivers.camera as camera
+import requests
+import config
+
+picture_base64 = camera.get_transfer_picture_base64()
+if picture_base64 is not None:
+ camera.remove_transfer_picture()
+ controller_id = config.CONTROLLER_ID
+ owner = "Controller: %s" % controller_id
+
+ response = requests.post(config.PICTURE_URL + '/' + controller_id, json={
+ "picture_base64": picture_base64
+ })
+ print response
+
+print picture_base64 is not None
diff --git a/controller/config/copy__init__.py.example b/controller/config/copy__init__.py.example
index 52ec8d4..244d6d9 100644
--- a/controller/config/copy__init__.py.example
+++ b/controller/config/copy__init__.py.example
@@ -1,5 +1,3 @@
-
-
GPIO_PIN_DHT = 4 # BCM
SENSORDATA_URL = 'http://agrar.zoblak.com/api/v1.0/sensorData'
GPIO_PIN_TANKLEVEL0 = 5 # BCM
@@ -14,4 +12,8 @@ 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'
+STATE_FILE = '/mnt/zoblakdata/controller_state'
+PICTURE_TRANSFER_FILE='/mnt/zoblakdata/picture_transfer_ready.jpg'
+PICTURE_INPUT_FILE='/mnt/zoblakdata/picture.jpg' # must match file in PICTURE_COMMAND
+PICTURE_COMMAND="""avconv -i rtsp://192.168.5.10:554//user=admin_password=_channel=1_stream=0.sdp -i /home/pi/projects/tfm/controller/zoblakLogo.png -filter_complex "[0:v]drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:fontsize=30:box=1:boxcolor=black@0.75:text='%d\.%m\.%Y\. %H\:%M\:%S':fontcolor=white@0.8: x=10: y=10[text]; [text][1:v]overlay=main_w-overlay_w-5:5 [filtered]" -map "[filtered]" -f image2 -vframes 1 /mnt/zoblakdata/picture.jpg""" # filename must match PICTURE_INPUT_FILE path
+PICTURE_URL = 'http://agrar.zoblak.com/api/v1.0/picture/'
diff --git a/controller/drivers/__init__.py b/controller/drivers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/controller/drivers/camera/__init__.py b/controller/drivers/camera/__init__.py
new file mode 100644
index 0000000..3fc347a
--- /dev/null
+++ b/controller/drivers/camera/__init__.py
@@ -0,0 +1,28 @@
+import config
+import os
+import base64;
+from shutil import copyfile
+import sys
+
+def capture_picture():
+ os.system(config.PICTURE_COMMAND)
+
+def get_transfer_picture_base64():
+ try:
+ with open(config.PICTURE_TRANSFER_FILE, "rb") as image_file:
+ return base64.b64encode(image_file.read())
+ except:
+ print("Unexpected error:", sys.exc_info()[0])
+ return None
+
+def remove_transfer_picture():
+ try:
+ os.remove(config.PICTURE_TRANSFER_FILE)
+ except:
+ print("Error removing: ", config.PICTURE_TRANSFER_FILE )
+
+def make_transfer_picture():
+ try:
+ copyfile(config.PICTURE_INPUT_FILE, config.PICTURE_TRANSFER_FILE)
+ except:
+ print("Error copying: ", config.PICTURE_INPUT_FILE, config.PICTURE_TRANSFER_FILE )
diff --git a/controller/sensors.py b/controller/sensors.py
index 9c8ff9d..70ea964 100644
--- a/controller/sensors.py
+++ b/controller/sensors.py
@@ -7,27 +7,27 @@ import RPi.GPIO as GPIO
# 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_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)
# 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)
+# 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 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)
@@ -42,6 +42,7 @@ stopPumpingAt = config.STOP_PUMPING_AT
# to 15 times to get a sensor reading (waiting 2 seconds between each retry).
humidity, temperature = Adafruit_DHT.read_retry(SENSOR_TYPE, config.GPIO_PIN_DHT)
+
# Un-comment the line below to convert the temperature to Fahrenheit.
# temperature = temperature * 9/5.0 + 32
@@ -50,7 +51,13 @@ humidity, temperature = Adafruit_DHT.read_retry(SENSOR_TYPE, config.GPIO_PIN_DHT
# guarantee the timing of calls to read the sensor).
# If this happens try again!
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",
+ 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)
diff --git a/controller/state/__init__.py b/controller/state/__init__.py
index 1c5ffb1..d1a50d5 100644
--- a/controller/state/__init__.py
+++ b/controller/state/__init__.py
@@ -37,7 +37,10 @@ def sync():
changer = Changer(local_state, server_state)
current_state = changer.process_change()
+ print "posting :" + repr(current_state)
+ local.data = current_state
server.post_state(current_state)
+ local.save()
except:
print " panicking safely ! "
safely_panic()
diff --git a/controller/state/changer.py b/controller/state/changer.py
index 3c6f4f0..f2b7714 100644
--- a/controller/state/changer.py
+++ b/controller/state/changer.py
@@ -1,5 +1,6 @@
import RPi.GPIO as GPIO
import config
+from drivers.camera import make_transfer_picture
class Changer(object):
@@ -45,6 +46,8 @@ class Changer(object):
if in_valve_change is not None:
in_valve_change()
+ self.fulfill_picture_request()
+
return self.local_state
def open_in_valve(self):
@@ -63,6 +66,12 @@ class Changer(object):
GPIO.output(config.GPIO_PIN_OUT_VALVE, GPIO.LOW)
self.local_state['out_valve'] = 'closed'
+ def fulfill_picture_request(self):
+ if self.remote_state['picture_requested'] == 'true':
+ make_transfer_picture()
+ self.local_state['picture_requested'] = 'false'
+
+
def validate_states(self):
if self.local_state is None or self.remote_state is None:
raise ClassNotReadyException("Both local and remote states must be present!")
diff --git a/controller/state/file.py b/controller/state/file.py
index 1172205..9bbe285 100644
--- a/controller/state/file.py
+++ b/controller/state/file.py
@@ -9,7 +9,8 @@ class File(object):
self.filename = filename
def present(self):
- os.path.isfile(self.filename)
+ return os.path.isfile(self.filename)
+
def load(self):
if self.filename is None:
diff --git a/controller/zoblakLogo.png b/controller/zoblakLogo.png
new file mode 100644
index 0000000..27112ff
Binary files /dev/null and b/controller/zoblakLogo.png differ