From c69219063b2424d7ebab7e09f6f7289d082f43b1 Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sat, 21 Oct 2017 12:36:59 +0200 Subject: [PATCH 01/34] Add dimmer support part 1. --- README.md | 18 +++++---- install.sh | 2 +- static/css/terrariumpi.css | 11 +++++- static/js/terrariumpi.js | 24 +++++++++++- terrariumCollector.py | 22 +++++++---- terrariumEngine.py | 4 +- terrariumSwitch.py | 79 ++++++++++++++++++++++++++++++++++---- terrariumWebserver.py | 13 +++++++ views/inc/footer.tpl | 2 + views/switch_settings.tpl | 1 + views/switch_status.tpl | 2 +- 11 files changed, 146 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index d8e490429..1928376f3 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Think off: And all this is controlled with a nice webinterface based on [Gentelella a Bootstrap 3 template](https://github.com/puikinsh/gentelella/). ## Features - Controlling electronic devices like lights, sprayers, heating and cooling equipment +- Support for dimming electronic devices - Reading out temperature and humidity sensors - Open door detection (sprayer will not spray when a door is open) - Support for native Raspberry Pi cam out of the box @@ -37,15 +38,15 @@ Your language not in the list? [Create your own language translation](https://gi ## Installation The installation expects a Pi with working network and ssh. It is tested with [Raspbian Stretch Lite](https://www.raspberrypi.org/downloads/raspbian/). For now the Full version is not working somehow.... So use the lite image! -1. Get a working Raspberry Pi and login as user 'pi' +1. Get a working Raspberry Pi and login as user 'pi' `ssh pi@[raspberry_ip]` -2. Clone this repository and submodules! +2. Clone this repository and submodules! `git clone --recursive https://github.com/theyosh/TerrariumPI.git` -3. Enter the new TerrariumPI folder +3. Enter the new TerrariumPI folder `cd TerrariumPI` -4. Run the installer script and wait +4. Run the installer script and wait `sudo ./install.sh` -5. Reboot Raspberry PI to get all the needed modules loaded +5. Reboot Raspberry PI to get all the needed modules loaded `sudo reboot` 6. Go to the webinterface at http://[raspberry_ip]:8090 @@ -55,11 +56,11 @@ Make sure that your Pi is secured when you put it to the Internet. Would be a sh ## Updating This updating is based on that the software is installed with the steps in the Installation above. -1. Get a working Raspberry Pi and login as user 'pi' +1. Get a working Raspberry Pi and login as user 'pi' `ssh pi@[raspberry_ip]` -2. Enter the TerrariumPI folder +2. Enter the TerrariumPI folder `cd TerrariumPI` -3. Update the new code with git +3. Update the new code with git `git pull` 4. Restart TerrariumPI according to: https://github.com/theyosh/TerrariumPI/wiki/FAQ#how-to-restart-terrariumpi @@ -70,6 +71,7 @@ This software requires some extra hardware in order to run and work. The bare mi - Power relay board - USB versions (Serial and Bitbang) - GPIO versions + - PWM Dimmer versions - Temperature/humdity sensors DHT11, DHT22, AM2303, DS1820, HIH4000, etc through - OWFS - GPIO diff --git a/install.sh b/install.sh index 2bfbe5c5c..9f3c15f72 100755 --- a/install.sh +++ b/install.sh @@ -15,7 +15,7 @@ apt-get -y autoremove # Install required packages to get the terrarium software running aptitude -y update aptitude -y safe-upgrade -aptitude -y install libftdi1 screen python-imaging python-dateutil python-ow python-rpi.gpio python-psutil git subversion watchdog build-essential python-dev python-picamera python-opencv python-pip +aptitude -y install libftdi1 screen python-imaging python-dateutil python-ow python-rpi.gpio python-psutil git subversion watchdog build-essential python-dev python-picamera python-opencv python-pip python-pigpio if [ `ls -l gentelella | grep -v ^t | wc -l` -eq 0 ]; then # Manual get Gentelella bootstrap 3 template diff --git a/static/css/terrariumpi.css b/static/css/terrariumpi.css index f43fbeda4..f78965f99 100644 --- a/static/css/terrariumpi.css +++ b/static/css/terrariumpi.css @@ -162,16 +162,23 @@ div.modal div.row.webcam{ text-align: center; font-size: 1.5em; display: inline; - margin: 0.15em 25%; } .power_switch span { cursor: pointer; } +.power_switch.dimmer { + display: block; + position: relative; +} + .power_switch.big, .fa.fa-lock.big { - font-size: 11em; + font-size: 12em; + display: block; + padding-top: 0.1em; + line-height: 1em; } .x_title .power_switch { diff --git a/static/js/terrariumpi.js b/static/js/terrariumpi.js index 18b98e196..000071a58 100644 --- a/static/js/terrariumpi.js +++ b/static/js/terrariumpi.js @@ -1213,9 +1213,29 @@ function add_webcam_row(id,location,name,rotation,preview) { function update_power_switch(id, data) { var power_switch = $('#switch_' + id); + var update_data = ''; + if (data.hardwaretype === 'pwm-dimmer') { + update_data = formatNumber(data.current_power_wattage) + 'W / ' + } + update_data += formatNumber(data.power_wattage) + 'W'; + if (data.water_flow > 0) { + update_data += ' - ' + formatNumber(data.water_flow) + 'L/m'; + } + power_switch.find('h2 span.title').text('{{_('Switch')}} ' + data.name); - power_switch.find('h2 small.data_update').text(formatNumber(data.power_wattage) + 'W' + (data.water_flow > 0 ? ' - ' + formatNumber(data.water_flow) + 'L/m' : '')); - power_switch.find('span.glyphicon').removeClass('blue green').addClass((data.state ? 'green' : 'blue')).attr('title','{{_('Toggle power switch')}}'); + power_switch.find('h2 small.data_update').text(update_data); + + if (data.hardwaretype === 'pwm-dimmer') { + power_switch.find('div.power_switch').removeClass('big').addClass('dimmer').html(''); + + power_switch.find('.knob').knob({ + release: function(value) { + $.getJSON('/api/switch/state/' + id + '/' + value,function(data){ + }); + } + }); + } + power_switch.find('span.glyphicon').removeClass('blue green').addClass((data.state ? 'green' : 'blue')); } function toggleSwitch(id) { diff --git a/terrariumCollector.py b/terrariumCollector.py index b52a01c52..621cbe862 100644 --- a/terrariumCollector.py +++ b/terrariumCollector.py @@ -117,6 +117,13 @@ def __log_data(self,type,id,newdata): if type in ['switches']: if 'time' in newdata: now = newdata['time'] + + # Make a duplicate of last state and save it with 1 sec back in time to smooth the graphs + cur.execute('''INSERT INTO switch_data (id,timestamp,state,power_wattage,water_flow) + SELECT id, ? as timestamp,state,power_wattage,water_flow + FROM switch_data + WHERE id = ? ORDER BY timestamp DESC LIMIT 1''', (now-1, id)) + cur.execute('REPLACE INTO switch_data (id, timestamp, state, power_wattage, water_flow) VALUES (?,?,?,?,?)', (id, now, newdata['state'], newdata['power_wattage'], newdata['water_flow'])) @@ -179,15 +186,14 @@ def log_switch_data(self,switch): del(switch['address']) del(switch['name']) - # Create new object for the previous state. Copy data and invert boolean value - if 'init' not in switch: - old_swich = switch.copy() - old_swich['state'] = not switch['state'] - old_swich['time'] = int(time.time()) - 1 - self.__log_data('switches',switch_id,old_swich) - else: - del(switch['init']) + # Rather updating a big database, we translate some fields here + switch['power_wattage'] = switch['current_power_wattage'] + switch['water_flow'] = switch['current_water_flow'] + del(switch['current_power_wattage']) + del(switch['current_water_flow']) + if 'init' in switch: + del(switch['init']) self.__log_data('switches',switch_id,switch) def log_door_data(self,door): diff --git a/terrariumEngine.py b/terrariumEngine.py index bebb4a418..9aa5a5dbf 100644 --- a/terrariumEngine.py +++ b/terrariumEngine.py @@ -186,10 +186,10 @@ def __get_power_usage_water_flow(self, socket = False): 'water' : {'current' : 0.0 , 'max' : 0.0}} for switchid in self.power_switches: - data['power']['current'] += self.power_switches[switchid].get_power_wattage() if self.power_switches[switchid].is_on() else 0.0 + data['power']['current'] += self.power_switches[switchid].get_current_power_wattage() if self.power_switches[switchid].is_on() else 0.0 data['power']['max'] += self.power_switches[switchid].get_power_wattage() - data['water']['current'] += self.power_switches[switchid].get_water_flow() if self.power_switches[switchid].is_on() else 0.0 + data['water']['current'] += self.power_switches[switchid].get_current_water_flow() if self.power_switches[switchid].is_on() else 0.0 data['water']['max'] += self.power_switches[switchid].get_water_flow() return data diff --git a/terrariumSwitch.py b/terrariumSwitch.py index b2f0bd20e..382c2567e 100644 --- a/terrariumSwitch.py +++ b/terrariumSwitch.py @@ -4,14 +4,17 @@ from pylibftdi import Driver, BitBangDevice, SerialDevice, Device import RPi.GPIO as GPIO +GPIO.setwarnings(False) +import pigpio from hashlib import md5 class terrariumSwitch(): - valid_hardware_types = ['ftdi','gpio','gpio-inverse'] + valid_hardware_types = ['ftdi','gpio','gpio-inverse','pwm-dimmer'] OFF = False ON = True + PWM_MAXDIM = 880 # http://www.esp8266-projects.com/2017/04/raspberry-pi-domoticz-ac-dimmer-part-1/ bitbang_addresses = { "1":"2", @@ -33,6 +36,8 @@ def __init__(self, id, hardware_type, address, name = '', power_wattage = 0, wat if self.get_hardware_type() == 'ftdi': self.__load_ftdi_device() + elif self.get_hardware_type() == 'pwm-dimmer': + self.__load_pwm_device() elif 'gpio' in self.get_hardware_type(): self.__load_gpio_device() @@ -50,7 +55,7 @@ def __init__(self, id, hardware_type, address, name = '', power_wattage = 0, wat self.get_water_flow())) # Force to off state! - self.init = True + #self.init = True self.state = None self.set_state(terrariumSwitch.OFF,True) @@ -64,6 +69,14 @@ def __load_ftdi_device(self): def __load_gpio_device(self): GPIO.setmode(GPIO.BOARD) + def __load_pwm_device(self): + #self.callback = None + # localhost will not work always due to IPv6. Explicit 127.0.0.1 host + self.__pigpio = pigpio.pi('127.0.0.1',8888) + if not self.__pigpio.connected: + logger.error('PiGPIOd process is not running') + self.__pigpio = False + def set_state(self, state, force = False): if self.get_state() is not state or force: if self.get_hardware_type() == 'ftdi': @@ -91,17 +104,31 @@ def set_state(self, state, force = False): elif self.get_hardware_type() == 'gpio': GPIO.output(int(self.get_address()), ( GPIO.HIGH if state is terrariumSwitch.ON else GPIO.LOW )) + elif self.get_hardware_type() == 'gpio-inverse': GPIO.output(int(self.get_address()), ( GPIO.LOW if state is terrariumSwitch.ON else GPIO.HIGH )) + elif self.get_hardware_type() == 'pwm-dimmer' and self.__pigpio is not False: + # State 100 = full on which means 0 dim. + # State is inverse of dim + if state is terrariumSwitch.ON: + state = 100.0 + elif state is terrariumSwitch.OFF or not (0 <= state <= 100): + state = 0.0 + + dim = terrariumSwitch.PWM_MAXDIM * ((100.0 - state) / 100.0) + self.__pigpio.hardware_PWM(int(self.get_address()), 5000, int(dim) * 1000) # 5000Hz state*1000% dutycycle + logger.info('Changed dimmer \'%s\' from %s to %s',self.get_name(),self.state,state) + self.state = state - logger.info('Toggle switch \'%s\' from %s',self.get_name(),('off to on' if self.is_on() else 'on to off')) + if self.get_hardware_type() != 'pwm-dimmer': + logger.info('Toggle switch \'%s\' from %s',self.get_name(),('off to on' if self.is_on() else 'on to off')) if self.callback is not None: data = self.get_data() - if self.init: - data['init'] = 1 - self.init = False + #if self.init: + # data['init'] = 1 + # self.init = False self.callback(data) @@ -116,7 +143,9 @@ def get_data(self): 'address' : self.get_address(), 'name' : self.get_name(), 'power_wattage' : self.get_power_wattage(), + 'current_power_wattage' : self.get_current_power_wattage(), 'water_flow' : self.get_water_flow(), + 'current_water_flow' : self.get_current_water_flow(), 'state' : self.get_state() } @@ -153,6 +182,15 @@ def set_name(self,name): def get_power_wattage(self): return self.power_wattage + def get_current_power_wattage(self): + wattage = 0.0 + if self.get_hardware_type() == 'pwm-dimmer': + wattage = self.get_power_wattage() * (self.get_state() / 100.0) + else: + wattage = self.get_power_wattage() + + return wattage + def set_power_wattage(self,value): try: self.power_wattage = float(value) @@ -162,6 +200,15 @@ def set_power_wattage(self,value): def get_water_flow(self): return self.water_flow + def get_current_water_flow(self): + waterflow = 0.0 + if self.get_hardware_type() == 'pwm-dimmer': + waterflow = self.get_water_flow() * (self.get_state() / 100.0) + else: + waterflow = self.get_water_flow() + + return waterflow + def set_water_flow(self,value): try: self.water_flow = float(value) @@ -177,10 +224,22 @@ def toggle(self): return None def is_on(self): - return self.get_state() is terrariumSwitch.ON + state = None + if self.get_hardware_type() == 'pwm-dimmer': + state = self.get_state() > 0.0 + else: + state = self.get_state() + + return state is terrariumSwitch.ON def is_off(self): - return self.get_state() is terrariumSwitch.OFF + state = None + if self.get_hardware_type() == 'pwm-dimmer': + state = self.get_state() == 0.0 + else: + state = self.get_state() + + return state is terrariumSwitch.OFF def on(self): if self.get_state() is None or self.is_off(): @@ -191,3 +250,7 @@ def off(self): if self.get_state() is None or self.is_on(): self.set_state(terrariumSwitch.OFF) return self.is_off() + + def dim(self,value): + if 0 <= value <= 100: + self.set_state(100 - value) diff --git a/terrariumWebserver.py b/terrariumWebserver.py index 6d369ca44..235740364 100644 --- a/terrariumWebserver.py +++ b/terrariumWebserver.py @@ -78,6 +78,12 @@ def __routes(self): apply=auth_basic(self.__authenticate,_('TerrariumPI') + ' ' + _('Authentication'),_('Authenticate to make any changes')) ) + self.__app.route('/api/switch/state//', + method=['GET'], + callback=self.__state_switch, + apply=auth_basic(self.__authenticate,_('TerrariumPI') + ' ' + _('Authentication'),_('Authenticate to make any changes')) + ) + self.__app.route('/api/config/', method=['PUT','POST','DELETE'], callback=self.__update_api_call, @@ -229,6 +235,13 @@ def __toggle_switch(self,switchid): return {'ok' : False} + def __state_switch(self,switchid,value): + if switchid in self.__terrariumEngine.power_switches: + self.__terrariumEngine.power_switches[switchid].set_state(value) + return {'ok' : True} + + return {'ok' : False} + def __logout_url(self): return {'ok' : True, 'title' : _('Log out'), diff --git a/views/inc/footer.tpl b/views/inc/footer.tpl index 9d41db035..46b20c61b 100644 --- a/views/inc/footer.tpl +++ b/views/inc/footer.tpl @@ -25,6 +25,8 @@ + + diff --git a/views/switch_settings.tpl b/views/switch_settings.tpl index ae001da9d..027631e40 100644 --- a/views/switch_settings.tpl +++ b/views/switch_settings.tpl @@ -81,6 +81,7 @@ + diff --git a/views/switch_status.tpl b/views/switch_status.tpl index 73236ca90..c6cad83ec 100644 --- a/views/switch_status.tpl +++ b/views/switch_status.tpl @@ -44,7 +44,7 @@
- +
From dbd8235092f409b418c48dae9e7587e6758e6115 Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sat, 21 Oct 2017 12:48:23 +0200 Subject: [PATCH 02/34] Updated supported hardware page --- ..._MPDMv4_1_3_Kit_top_1.jpg.2560x2560_q85.jpg | Bin 0 -> 144584 bytes views/hardware.tpl | 9 +++++++++ 2 files changed, 9 insertions(+) create mode 100644 static/images/documentation/AC_Dimmer_MPDMv4_1_3_Kit_top_1.jpg.2560x2560_q85.jpg diff --git a/static/images/documentation/AC_Dimmer_MPDMv4_1_3_Kit_top_1.jpg.2560x2560_q85.jpg b/static/images/documentation/AC_Dimmer_MPDMv4_1_3_Kit_top_1.jpg.2560x2560_q85.jpg new file mode 100644 index 0000000000000000000000000000000000000000..51a7b819273cbd57c13a5837d94199d20e9f01c8 GIT binary patch literal 144584 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�UAhoU4*>2Q9BZlCHj5Iyf<@wm~Kb1 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 w4Y=%c!4pZGP#3)K{X^cG0X?wjN{hN_Y`60;MK8 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'Denkovi')}}: ...

+
Dashboard screenshot From 8104c234206bf0cb94f6910134f4a67b044c17e3 Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sat, 21 Oct 2017 16:01:24 +0200 Subject: [PATCH 03/34] Updated dimmer settings in switch settings page --- static/js/terrariumpi.js | 19 ++++++-- terrariumSwitch.py | 91 +++++++++++++++++++++++++++++++++------ views/switch_settings.tpl | 81 +++++++++++++++++++++++----------- 3 files changed, 147 insertions(+), 44 deletions(-) diff --git a/static/js/terrariumpi.js b/static/js/terrariumpi.js index 000071a58..96aa96879 100644 --- a/static/js/terrariumpi.js +++ b/static/js/terrariumpi.js @@ -1116,12 +1116,17 @@ function add_switch() { form.find('input[name="switch_[nr]_address"]').val(), form.find('input[name="switch_[nr]_name"]').val(), form.find('input[name="switch_[nr]_power_wattage"]').val(), - form.find('input[name="switch_[nr]_water_flow"]').val()); + form.find('input[name="switch_[nr]_water_flow"]').val(), + form.find('input[name="switch_[nr]_dimmer_on_duration"]').val(), + form.find('input[name="switch_[nr]_dimmer_on_percentage"]').val(), + form.find('input[name="switch_[nr]_dimmer_off_duration"]').val(), + form.find('input[name="switch_[nr]_dimmer_off_percentage"]').val(), + ); $('.new-switch-form').modal('hide'); } -function add_switch_row(id,hardwaretype,address,name,power_wattage,water_flow) { +function add_switch_row(id,hardwaretype,address,name,power_wattage,water_flow, dimmer_on_duration,dimmer_on_percentage,dimmer_off_duration,dimmer_off_percentage) { var switch_row = $($('.modal-body div.row.switch').parent().clone().html().replace(/\[nr\]/g, $('form div.row.switch').length)); switch_row.find('div.power_switch.small').attr('id','switch_' + id); @@ -1129,7 +1134,7 @@ function add_switch_row(id,hardwaretype,address,name,power_wattage,water_flow) { switch_row.find('.x_title').show().find('h2 small').text(name); switch_row.find('span.select2.select2-container').remove(); - switch_row.find('input, select').each(function(counter,item){ + switch_row.find('input, select').each(function(counter,item) { $(item).val(eval($(item).attr('name').replace(/switch_[0-9]+_/g,''))); }); @@ -1140,7 +1145,10 @@ function add_switch_row(id,hardwaretype,address,name,power_wattage,water_flow) { placeholder: '{{_('Select an option')}}', allowClear: false, minimumResultsForSearch: Infinity + }).on('change',function() { + switch_row.find('.row.dimmer').toggle(this.value === 'pwm-dimmer'); }); + switch_row.find('.row.dimmer').toggle(hardwaretype === 'pwm-dimmer'); } function add_door() { @@ -1226,12 +1234,15 @@ function update_power_switch(id, data) { power_switch.find('h2 small.data_update').text(update_data); if (data.hardwaretype === 'pwm-dimmer') { - power_switch.find('div.power_switch').removeClass('big').addClass('dimmer').html(''); + power_switch.find('div.power_switch').removeClass('big').addClass('dimmer').html(''); power_switch.find('.knob').knob({ release: function(value) { $.getJSON('/api/switch/state/' + id + '/' + value,function(data){ }); + }, + format: function(value) { + return value + '%'; } }); } diff --git a/terrariumSwitch.py b/terrariumSwitch.py index 382c2567e..0cf6a723d 100644 --- a/terrariumSwitch.py +++ b/terrariumSwitch.py @@ -7,6 +7,9 @@ GPIO.setwarnings(False) import pigpio from hashlib import md5 +import thread +import time +import math class terrariumSwitch(): @@ -14,7 +17,11 @@ class terrariumSwitch(): OFF = False ON = True - PWM_MAXDIM = 880 # http://www.esp8266-projects.com/2017/04/raspberry-pi-domoticz-ac-dimmer-part-1/ + + # PWM Dimmer settings + PWM_DIMMER_MAXDIM = 880 # http://www.esp8266-projects.com/2017/04/raspberry-pi-domoticz-ac-dimmer-part-1/ + PWM_DIMMER_MIN_TIMEOUT=0.3 + PWM_DIMMER_MIN_STEP=1.0 bitbang_addresses = { "1":"2", @@ -55,7 +62,6 @@ def __init__(self, id, hardware_type, address, name = '', power_wattage = 0, wat self.get_water_flow())) # Force to off state! - #self.init = True self.state = None self.set_state(terrariumSwitch.OFF,True) @@ -70,13 +76,58 @@ def __load_gpio_device(self): GPIO.setmode(GPIO.BOARD) def __load_pwm_device(self): - #self.callback = None + self.__dimmer_running = False # localhost will not work always due to IPv6. Explicit 127.0.0.1 host self.__pigpio = pigpio.pi('127.0.0.1',8888) if not self.__pigpio.connected: logger.error('PiGPIOd process is not running') self.__pigpio = False + def __dim_switch(self,from_value,to_value): + # When the dimmer is working, ignore new state changes. + if not self.__dimmer_running: + self.__dimmer_running = True + + if from_value is None: + logger.info('Switching dimmer \'%s\' from %s%% to %s%% instantly', + self.get_name(),from_value,to_value) + # Geen animatie, gelijk to_value + dim_value = terrariumSwitch.PWM_DIMMER_MAXDIM * ((100.0 - to_value) / 100.0) + self.__pigpio.hardware_PWM(int(self.get_address()), 5000, int(dim_value) * 1000) # 5000Hz state*1000% dutycycle + else: + direction = (1 if from_value < to_value else -1) + duration = (self.get_dimmer_on_duration() if direction == 1 else self.get_dimmer_off_duration()) + + logger.info('Changing dimmer \'%s\' from %s%% to %s%% in %s seconds', + self.get_name(),from_value,to_value,duration) + + distance = abs(from_value - to_value) + if duration == 0: + steps = 1 + else: + steps = math.floor(min( (abs(duration) / terrariumSwitch.PWM_DIMMER_MIN_TIMEOUT), + (distance / terrariumSwitch.PWM_DIMMER_MIN_STEP))) + distance /= steps + duration /= steps + + logger.debug('Dimmer settings: Steps: %s, Distance per step: %s%%, Time per step: %s, Direction: %s',steps, distance, duration, direction) + + for counter in range(int(steps)): + from_value += (direction * distance) + dim_value = terrariumSwitch.PWM_DIMMER_MAXDIM * ((100.0 - from_value) / 100.0) + logger.debug('Dimmer animation: Step: %s, value %s%%, Dim value: %s, timeout %s',counter+1, from_value, dim_value, duration) + self.__pigpio.hardware_PWM(int(self.get_address()), 5000, int(dim_value) * 1000) # 5000Hz state*1000% dutycycle + time.sleep(duration) + + # For impatient people... Put the dimmer at the current state value if it has changed during the animation + dim_value = terrariumSwitch.PWM_DIMMER_MAXDIM * ((100.0 - self.get_state()) / 100.0) + self.__pigpio.hardware_PWM(int(self.get_address()), 5000, int(dim_value) * 1000) # 5000Hz state*1000% dutycycle + + self.__dimmer_running = False + logger.info('Dimmer \'%s\' is done at value %s%%',self.get_name(),self.get_state()) + else: + logger.warning('Dimmer %s is already working. Ignoring state change!. Will switch to latest state value when done', self.get_name()) + def set_state(self, state, force = False): if self.get_state() is not state or force: if self.get_hardware_type() == 'ftdi': @@ -112,24 +163,17 @@ def set_state(self, state, force = False): # State 100 = full on which means 0 dim. # State is inverse of dim if state is terrariumSwitch.ON: - state = 100.0 + state = self.get_dimmer_on_percentage() elif state is terrariumSwitch.OFF or not (0 <= state <= 100): - state = 0.0 + state = self.get_dimmer_off_percentage() - dim = terrariumSwitch.PWM_MAXDIM * ((100.0 - state) / 100.0) - self.__pigpio.hardware_PWM(int(self.get_address()), 5000, int(dim) * 1000) # 5000Hz state*1000% dutycycle - logger.info('Changed dimmer \'%s\' from %s to %s',self.get_name(),self.state,state) + thread.start_new_thread(self.__dim_switch, (self.state,state)) self.state = state if self.get_hardware_type() != 'pwm-dimmer': logger.info('Toggle switch \'%s\' from %s',self.get_name(),('off to on' if self.is_on() else 'on to off')) if self.callback is not None: data = self.get_data() - - #if self.init: - # data['init'] = 1 - # self.init = False - self.callback(data) return self.get_state() == state @@ -146,7 +190,11 @@ def get_data(self): 'current_power_wattage' : self.get_current_power_wattage(), 'water_flow' : self.get_water_flow(), 'current_water_flow' : self.get_current_water_flow(), - 'state' : self.get_state() + 'state' : self.get_state(), + 'dimmer_on_duration': self.get_dimmer_on_duration(), + 'dimmer_on_percentage' : self.get_dimmer_on_percentage(), + 'dimmer_off_duration': self.get_dimmer_off_duration(), + 'dimmer_off_percentage': self.get_dimmer_off_percentage() } return data @@ -254,3 +302,18 @@ def off(self): def dim(self,value): if 0 <= value <= 100: self.set_state(100 - value) + + #def get_dimmer_duration(self): + # return (15 if self.get_hardware_type() == 'pwm-dimmer' else 0) + + def get_dimmer_on_duration(self): + return (15 if self.get_hardware_type() == 'pwm-dimmer' else 0) + + def get_dimmer_off_duration(self): + return (5 if self.get_hardware_type() == 'pwm-dimmer' else 0) + + def get_dimmer_on_percentage(self): + return (60 if self.get_hardware_type() == 'pwm-dimmer' else 100) + + def get_dimmer_off_percentage(self): + return (0 if self.get_hardware_type() == 'pwm-dimmer' else 0) diff --git a/views/switch_settings.tpl b/views/switch_settings.tpl index 027631e40..9088c8a73 100644 --- a/views/switch_settings.tpl +++ b/views/switch_settings.tpl @@ -74,33 +74,53 @@
-
- -
- +
+
+ +
+ +
+
+
+ * + + +
+
+ * + +
+
+ + +
+
+ +
-
- * - - -
-
- * - -
-
- - -
-
- - +
@@ -117,10 +137,13 @@ From e0f1b9c9f321d1939f8fdbe52cdeb3be8d48b880 Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sat, 21 Oct 2017 17:02:59 +0200 Subject: [PATCH 04/34] Add dimmer settings processing and saving to config file --- terrariumConfig.py | 6 ++++++ terrariumEngine.py | 12 ++++++++++++ terrariumSwitch.py | 40 ++++++++++++++++++++++++++++------------ 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/terrariumConfig.py b/terrariumConfig.py index 7e3c1ec2a..a0c9bb99c 100644 --- a/terrariumConfig.py +++ b/terrariumConfig.py @@ -291,6 +291,12 @@ def save_power_switch(self,data): if 'state' in data: del(data['state']) + if 'current_power_wattage' in data: + del(data['current_power_wattage']) + + if 'current_water_flow' in data: + del(data['current_water_flow']) + return self.__update_config('switch' + str(data['id']),data) def save_power_switches(self,data): diff --git a/terrariumEngine.py b/terrariumEngine.py index 9aa5a5dbf..1ec6fbea7 100644 --- a/terrariumEngine.py +++ b/terrariumEngine.py @@ -118,6 +118,10 @@ def __load_power_switches(self,reloading = False): self.power_switches[switch_config[power_switch_config]['id']].set_name(switch_config[power_switch_config]['name']) self.power_switches[switch_config[power_switch_config]['id']].set_power_wattage(switch_config[power_switch_config]['power_wattage']) self.power_switches[switch_config[power_switch_config]['id']].set_water_flow(switch_config[power_switch_config]['water_flow']) + self.power_switches[switch_config[power_switch_config]['id']].set_dimmer_on_duration(switch_config[power_switch_config]['dimmer_on_duration']) + self.power_switches[switch_config[power_switch_config]['id']].set_dimmer_on_percentage(switch_config[power_switch_config]['dimmer_on_percentage']) + self.power_switches[switch_config[power_switch_config]['id']].set_dimmer_off_duration(switch_config[power_switch_config]['dimmer_off_duration']) + self.power_switches[switch_config[power_switch_config]['id']].set_dimmer_off_percentage(switch_config[power_switch_config]['dimmer_off_percentage']) else: power_switch = terrariumSwitch( @@ -127,6 +131,10 @@ def __load_power_switches(self,reloading = False): switch_config[power_switch_config]['name'], switch_config[power_switch_config]['power_wattage'], switch_config[power_switch_config]['water_flow'], + switch_config[power_switch_config]['dimmer_on_duration'], + switch_config[power_switch_config]['dimmer_on_percentage'], + switch_config[power_switch_config]['dimmer_off_duration'], + switch_config[power_switch_config]['dimmer_off_percentage'], self.toggle_switch ) self.power_switches[power_switch.get_id()] = power_switch @@ -406,6 +414,10 @@ def set_switches_config(self, data): power_switch.set_name(switchdata['name']) power_switch.set_power_wattage(switchdata['power_wattage']) power_switch.set_water_flow(switchdata['water_flow']) + power_switch.set_dimmer_on_duration(switchdata['dimmer_on_duration']) + power_switch.set_dimmer_on_percentage(switchdata['dimmer_on_percentage']) + power_switch.set_dimmer_off_duration(switchdata['dimmer_off_duration']) + power_switch.set_dimmer_off_percentage(switchdata['dimmer_off_percentage']) new_switches[power_switch.get_id()] = power_switch diff --git a/terrariumSwitch.py b/terrariumSwitch.py index 0cf6a723d..0327ac03d 100644 --- a/terrariumSwitch.py +++ b/terrariumSwitch.py @@ -35,7 +35,7 @@ class terrariumSwitch(): "all":"FF" } - def __init__(self, id, hardware_type, address, name = '', power_wattage = 0, water_flow = 0, callback = None): + def __init__(self, id, hardware_type, address, name = '', power_wattage = 0.0, water_flow = 0.0, dimmer_on_duration = 0.0, dimmer_on_percentage = 100.0, dimmer_off_duration = 0.0, dimmer_off_percentage = 0.0, callback = None): self.id = id self.callback = callback @@ -53,6 +53,11 @@ def __init__(self, id, hardware_type, address, name = '', power_wattage = 0, wat self.set_power_wattage(power_wattage) self.set_water_flow(water_flow) + self.set_dimmer_on_duration(dimmer_on_duration) + self.set_dimmer_on_percentage(dimmer_on_percentage) + self.set_dimmer_off_duration(dimmer_off_duration) + self.set_dimmer_off_percentage(dimmer_off_percentage) + if self.id is None: self.id = md5(b'' + self.get_hardware_type() + self.get_address()).hexdigest() @@ -92,18 +97,20 @@ def __dim_switch(self,from_value,to_value): logger.info('Switching dimmer \'%s\' from %s%% to %s%% instantly', self.get_name(),from_value,to_value) # Geen animatie, gelijk to_value - dim_value = terrariumSwitch.PWM_DIMMER_MAXDIM * ((100.0 - to_value) / 100.0) + dim_value = terrariumSwitch.PWM_DIMMER_MAXDIM * ((100.0 - float(to_value)) / 100.0) self.__pigpio.hardware_PWM(int(self.get_address()), 5000, int(dim_value) * 1000) # 5000Hz state*1000% dutycycle else: - direction = (1 if from_value < to_value else -1) - duration = (self.get_dimmer_on_duration() if direction == 1 else self.get_dimmer_off_duration()) + from_value = float(from_value) + to_value = float(to_value) + direction = (1.0 if from_value < to_value else -1.0) + duration = (self.get_dimmer_on_duration() if direction == 1.0 else self.get_dimmer_off_duration()) logger.info('Changing dimmer \'%s\' from %s%% to %s%% in %s seconds', self.get_name(),from_value,to_value,duration) distance = abs(from_value - to_value) - if duration == 0: - steps = 1 + if duration == 0.0: + steps = 1.0 else: steps = math.floor(min( (abs(duration) / terrariumSwitch.PWM_DIMMER_MIN_TIMEOUT), (distance / terrariumSwitch.PWM_DIMMER_MIN_STEP))) @@ -303,17 +310,26 @@ def dim(self,value): if 0 <= value <= 100: self.set_state(100 - value) - #def get_dimmer_duration(self): - # return (15 if self.get_hardware_type() == 'pwm-dimmer' else 0) + def set_dimmer_on_duration(self,value): + self.__dimmer_on_duration = float(value if float(value) >= 0.0 else 0) def get_dimmer_on_duration(self): - return (15 if self.get_hardware_type() == 'pwm-dimmer' else 0) + return (self.__dimmer_on_duration if self.get_hardware_type() == 'pwm-dimmer' else 0.0) + + def set_dimmer_off_duration(self,value): + self.__dimmer_off_duration = float(value if float(value) >= 0.0 else 0) def get_dimmer_off_duration(self): - return (5 if self.get_hardware_type() == 'pwm-dimmer' else 0) + return (self.__dimmer_off_duration if self.get_hardware_type() == 'pwm-dimmer' else 0.0) + + def set_dimmer_on_percentage(self,value): + self.__dimmer_on_percentage = float(value if (0.0 <= float(value) <= 100.0) else 100) def get_dimmer_on_percentage(self): - return (60 if self.get_hardware_type() == 'pwm-dimmer' else 100) + return (self.__dimmer_on_percentage if self.get_hardware_type() == 'pwm-dimmer' else 100.0) + + def set_dimmer_off_percentage(self,value): + self.__dimmer_off_percentage = float(value if (0.0 <= float(value) <= 100.0) else 100) def get_dimmer_off_percentage(self): - return (0 if self.get_hardware_type() == 'pwm-dimmer' else 0) + return (self.__dimmer_off_percentage if self.get_hardware_type() == 'pwm-dimmer' else 0.0) From 620f1f01f5ff7ddd04f3c98abaa23c6dfa7ca24f Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sat, 21 Oct 2017 17:41:41 +0200 Subject: [PATCH 05/34] Fix SQL duplicate key error --- terrariumCollector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terrariumCollector.py b/terrariumCollector.py index 621cbe862..ee1754eab 100644 --- a/terrariumCollector.py +++ b/terrariumCollector.py @@ -119,7 +119,7 @@ def __log_data(self,type,id,newdata): now = newdata['time'] # Make a duplicate of last state and save it with 1 sec back in time to smooth the graphs - cur.execute('''INSERT INTO switch_data (id,timestamp,state,power_wattage,water_flow) + cur.execute('''REPLACE INTO switch_data (id,timestamp,state,power_wattage,water_flow) SELECT id, ? as timestamp,state,power_wattage,water_flow FROM switch_data WHERE id = ? ORDER BY timestamp DESC LIMIT 1''', (now-1, id)) From 1fb13db105c8aad8ab74711a57df9fae884a58d4 Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sat, 21 Oct 2017 17:42:13 +0200 Subject: [PATCH 06/34] Fix devision by zero --- terrariumSwitch.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/terrariumSwitch.py b/terrariumSwitch.py index 0327ac03d..b5301a712 100644 --- a/terrariumSwitch.py +++ b/terrariumSwitch.py @@ -105,11 +105,10 @@ def __dim_switch(self,from_value,to_value): direction = (1.0 if from_value < to_value else -1.0) duration = (self.get_dimmer_on_duration() if direction == 1.0 else self.get_dimmer_off_duration()) - logger.info('Changing dimmer \'%s\' from %s%% to %s%% in %s seconds', - self.get_name(),from_value,to_value,duration) + logger.info('Changing dimmer \'%s\' from %s%% to %s%% in %s seconds',self.get_name(),from_value,to_value,duration) distance = abs(from_value - to_value) - if duration == 0.0: + if duration == 0.0 or distance == 0.0: steps = 1.0 else: steps = math.floor(min( (abs(duration) / terrariumSwitch.PWM_DIMMER_MIN_TIMEOUT), From ee98d616fdcd20cc4f18435efe734dba8c7d710a Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sat, 21 Oct 2017 17:42:54 +0200 Subject: [PATCH 07/34] Added missing translation fields --- locales/en_US/LC_MESSAGES/terrariumpi.mo | Bin 59369 -> 60643 bytes locales/en_US/LC_MESSAGES/terrariumpi.po | 248 ++++++++++++++--------- locales/terrariumpi.pot | 246 +++++++++++++--------- terrariumTranslations.py | 4 + 4 files changed, 305 insertions(+), 193 deletions(-) diff --git a/locales/en_US/LC_MESSAGES/terrariumpi.mo b/locales/en_US/LC_MESSAGES/terrariumpi.mo index c1ec932d58349d075c6db7c55ec99fe5a8ea531e..e863b6a1bd056bfaa6b86176fde90fa306df503b 100644 GIT binary patch delta 11040 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 delta 9935 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\n" "Language-Team: \n" "Language: en_US\n" @@ -80,55 +80,71 @@ msgstr "Holds the switch power usage in Watt when switched on." msgid "Holds the switch water flow in liters per minute when switched on" msgstr "Holds the switch water flow in liters per minute when switched on" +#: terrariumTranslations.py:43 +msgid "Holds the amount of seconds for the duration in which it increases the power." +msgstr "Holds the amount of seconds for the duration in which it increases the power." + +#: terrariumTranslations.py:44 +msgid "Holds the amount in percentage to go to when switched on." +msgstr "Holds the amount in percentage to go to when switched on." + +#: terrariumTranslations.py:45 +msgid "Holds the amount of seconds for the duration in which it decresses the power." +msgstr "Holds the amount of seconds for the duration in which it decresses the power." + #: terrariumTranslations.py:46 +msgid "Holds the amount in percentage to go to when switched off." +msgstr "Holds the amount in percentage to go to when switched off." + +#: terrariumTranslations.py:50 msgid "Holds the door hardware type. Supported hardware types are: %s." msgstr "Holds the door hardware type. Supported hardware types are: %s." -#: terrariumTranslations.py:47 +#: terrariumTranslations.py:51 msgid "Holds the door address. When using GPIO use Physical GPIO pin numbering as address." msgstr "Holds the door address. When using GPIO use Physical GPIO pin numbering as address." -#: terrariumTranslations.py:48 +#: terrariumTranslations.py:52 msgid "Holds the door name." msgstr "Holds the door name." -#: terrariumTranslations.py:52 +#: terrariumTranslations.py:56 msgid "Holds the webcam location source. Supported sources are: %s" msgstr "Holds the webcam location source. Supported sources are: %s" -#: terrariumTranslations.py:53 +#: terrariumTranslations.py:57 msgid "Holds the webcam name." msgstr "Holds the webcam name." -#: terrariumTranslations.py:54 +#: terrariumTranslations.py:58 msgid "Holds the webcam rotation of the image." msgstr "Holds the webcam rotation of the image." -#: terrariumTranslations.py:55 +#: terrariumTranslations.py:59 msgid "Shows the webcam preview image." msgstr "Shows the webcam preview image." -#: terrariumTranslations.py:59 +#: terrariumTranslations.py:63 msgid "Enable or disable the light system. When enabled, you can make changes below. By disabling it will not loose the current settings. It will temporary stop the lighting system." msgstr "Enable or disable the light system. When enabled, you can make changes below. By disabling it will not loose the current settings. It will temporary stop the lighting system." -#: terrariumTranslations.py:60 +#: terrariumTranslations.py:64 msgid "Select the mode on which the lights will be put on and off. Select '%s' to use the sun rise and sun set at your location. This will make the amount of lighting variable to the actual amount of daylight. When selecting '%s', the light will put on and off at selected times." msgstr "Select the mode on which the lights will be put on and off. Select '%s' to use the sun rise and sun set at your location. This will make the amount of lighting variable to the actual amount of daylight. When selecting '%s', the light will put on and off at selected times." -#: terrariumTranslations.py:60 terrariumTranslations.py:61 -#: terrariumTranslations.py:62 terrariumTranslations.py:80 -#: terrariumTranslations.py:81 terrariumTranslations.py:82 -#: terrariumTranslations.py:89 terrariumTranslations.py:90 -#: terrariumTranslations.py:91 views/inc/usage_environment.tpl:33 +#: terrariumTranslations.py:64 terrariumTranslations.py:65 +#: terrariumTranslations.py:66 terrariumTranslations.py:84 +#: terrariumTranslations.py:85 terrariumTranslations.py:86 +#: terrariumTranslations.py:93 terrariumTranslations.py:94 +#: terrariumTranslations.py:95 views/inc/usage_environment.tpl:33 #: views/inc/usage_environment.tpl:229 views/inc/usage_environment.tpl:323 #: views/system_environment.tpl:46 views/system_environment.tpl:202 #: views/system_environment.tpl:277 msgid "Timer" msgstr "Timer" -#: terrariumTranslations.py:60 terrariumTranslations.py:65 -#: terrariumTranslations.py:80 terrariumTranslations.py:89 +#: terrariumTranslations.py:64 terrariumTranslations.py:69 +#: terrariumTranslations.py:84 terrariumTranslations.py:93 #: views/inc/menu.tpl:32 views/inc/usage_environment.tpl:34 #: views/inc/usage_environment.tpl:230 views/inc/usage_environment.tpl:324 #: views/inc/usage_weather.tpl:13 views/inc/usage_weather.tpl:82 @@ -139,71 +155,71 @@ msgstr "Timer" msgid "Weather" msgstr "Weather" -#: terrariumTranslations.py:61 +#: terrariumTranslations.py:65 msgid "Enter the time when the light should be put on. Only available when running in '%s' mode." msgstr "Enter the time when the light should be put on. Only available when running in '%s' mode." -#: terrariumTranslations.py:62 +#: terrariumTranslations.py:66 msgid "Enter the time when the lights should be put off. Only available when running in '%s' mode." msgstr "Enter the time when the lights should be put off. Only available when running in '%s' mode." -#: terrariumTranslations.py:63 +#: terrariumTranslations.py:67 msgid "Enter the maximum amount of lights time in hours. When the time between on and off is more then this maximum, the on and off time will be shifted more to each other." msgstr "Enter the maximum amount of lights time in hours. When the time between on and off is more then this maximum, the on and off time will be shifted more to each other." -#: terrariumTranslations.py:64 +#: terrariumTranslations.py:68 msgid "Enter the minimum amount of lights time in hours. When the time between on and off is less then this amount of hours, the on and off time will be widened until the minimum amount of hours entered here." msgstr "Enter the minimum amount of lights time in hours. When the time between on and off is less then this amount of hours, the on and off time will be widened until the minimum amount of hours entered here." -#: terrariumTranslations.py:65 +#: terrariumTranslations.py:69 msgid "Enter the amount of hours that the lights should shift. Is only needed when running in the '%s' mode. Enter a positive number for adding hours to the start time. Use negative numbers for subtracting from the start time." msgstr "Enter the amount of hours that the lights should shift. Is only needed when running in the '%s' mode. Enter a positive number for adding hours to the start time. Use negative numbers for subtracting from the start time." -#: terrariumTranslations.py:66 +#: terrariumTranslations.py:70 msgid "Select the power switches that should be toggled on the selected times above. Normally these are the switches connected to the lights. Select all needed switches below." msgstr "Select the power switches that should be toggled on the selected times above. Normally these are the switches connected to the lights. Select all needed switches below." -#: terrariumTranslations.py:69 +#: terrariumTranslations.py:73 msgid "Enable or disable the sprayer system. When enabled, you can make changes below. By disabling it will not loose the current settings. It will temporary stop the sprayer system." msgstr "Enable or disable the sprayer system. When enabled, you can make changes below. By disabling it will not loose the current settings. It will temporary stop the sprayer system." -#: terrariumTranslations.py:70 +#: terrariumTranslations.py:74 msgid "Enable spraying when the lights are off. This can cause water flow when there is not enough heat to vaporize the water." msgstr "Enable spraying when the lights are off. This can cause water flow when there is not enough heat to vaporize the water." -#: terrariumTranslations.py:71 +#: terrariumTranslations.py:75 msgid "Select the operating mode. For now only sensor mode is available." msgstr "Select the operating mode. For now only sensor mode is available." -#: terrariumTranslations.py:72 +#: terrariumTranslations.py:76 msgid "How much time must there be between two spray actions and after start up. Enter the amount of seconds in which the humidity can settle." msgstr "How much time must there be between two spray actions and after start up. Enter the amount of seconds in which the humidity can settle." -#: terrariumTranslations.py:73 +#: terrariumTranslations.py:77 msgid "How long is the system spraying. Enter the amount of seconds that the system is on when the humidity is too low." msgstr "How long is the system spraying. Enter the amount of seconds that the system is on when the humidity is too low." -#: terrariumTranslations.py:74 +#: terrariumTranslations.py:78 msgid "Select the power switches that should be toggled on the selected times above. Normally these are the switches connected to the sprayer. Select all needed switches below." msgstr "Select the power switches that should be toggled on the selected times above. Normally these are the switches connected to the sprayer. Select all needed switches below." -#: terrariumTranslations.py:75 +#: terrariumTranslations.py:79 msgid "Select the humidity sensors that are used to control the humidity. When selecting multiple sensors, the average is calculated to determine the final humidity." msgstr "Select the humidity sensors that are used to control the humidity. When selecting multiple sensors, the average is calculated to determine the final humidity." -#: terrariumTranslations.py:78 +#: terrariumTranslations.py:82 msgid "Enable or disable the heater system. When enabled, you can make changes below. By disabling it will not loose the current settings. It will temporary stop the heater system." msgstr "Enable or disable the heater system. When enabled, you can make changes below. By disabling it will not loose the current settings. It will temporary stop the heater system." -#: terrariumTranslations.py:79 +#: terrariumTranslations.py:83 msgid "Enable heating when the lights are on. This can cause overheating when the lights are on." msgstr "Enable heating when the lights are on. This can cause overheating when the lights are on." -#: terrariumTranslations.py:80 terrariumTranslations.py:89 +#: terrariumTranslations.py:84 terrariumTranslations.py:93 msgid "Select the operating mode. Use '%s' mode to select the time period in which the heating is running. Select '%s' mode to use the sun rise and sun set as on and off times. When the sun rises the heating system will stop. Use '%s' mode to have the heating running when the lights are off." msgstr "Select the operating mode. Use '%s' mode to select the time period in which the heating is running. Select '%s' mode to use the sun rise and sun set as on and off times. When the sun rises the heating system will stop. Use '%s' mode to have the heating running when the lights are off." -#: terrariumTranslations.py:80 terrariumTranslations.py:89 +#: terrariumTranslations.py:84 terrariumTranslations.py:93 #: views/inc/usage_environment.tpl:137 views/inc/usage_environment.tpl:231 #: views/inc/usage_environment.tpl:325 views/inc/usage_sensors.tpl:23 #: views/sensor_settings.tpl:74 views/system_environment.tpl:129 @@ -211,152 +227,154 @@ msgstr "Select the operating mode. Use '%s' mode to select the time period in wh msgid "Sensor" msgstr "Sensor" -#: terrariumTranslations.py:81 +#: terrariumTranslations.py:85 msgid "Enter the time when the heater should be put on. Only available when running in '%s' mode." msgstr "Enter the time when the heater should be put on. Only available when running in '%s' mode." -#: terrariumTranslations.py:82 +#: terrariumTranslations.py:86 msgid "Enter the time when the heater should be put off. Only available when running in '%s' mode." msgstr "Enter the time when the heater should be put off. Only available when running in '%s' mode." -#: terrariumTranslations.py:83 +#: terrariumTranslations.py:87 msgid "Select the power switches that should be toggled on the selected times above. Normally these are the switches connected to the heater. Select all needed switches below." msgstr "Select the power switches that should be toggled on the selected times above. Normally these are the switches connected to the heater. Select all needed switches below." -#: terrariumTranslations.py:84 terrariumTranslations.py:93 +#: terrariumTranslations.py:88 terrariumTranslations.py:97 msgid "Select the temperature sensors that are used to control the temperature. When selecting multiple sensors, the average is calculated to determine the final temperature." msgstr "Select the temperature sensors that are used to control the temperature. When selecting multiple sensors, the average is calculated to determine the final temperature." -#: terrariumTranslations.py:87 +#: terrariumTranslations.py:91 msgid "Enable or disable the cooler system. When enabled, you can make changes below. By disabling it will not loose the current settings. It will temporary stop the cooler system." msgstr "Enable or disable the cooler system. When enabled, you can make changes below. By disabling it will not loose the current settings. It will temporary stop the cooler system." -#: terrariumTranslations.py:88 +#: terrariumTranslations.py:92 msgid "Enable cooling when the lights are off. This can cause a very low temperature when the lights are off." msgstr "Enable cooling when the lights are off. This can cause a very low temperature when the lights are off." -#: terrariumTranslations.py:90 +#: terrariumTranslations.py:94 msgid "Enter the time when the cooler should be put on. Only available when running in '%s' mode." msgstr "Enter the time when the cooler should be put on. Only available when running in '%s' mode." -#: terrariumTranslations.py:91 +#: terrariumTranslations.py:95 msgid "Enter the time when the cooler should be put off. Only available when running in '%s' mode." msgstr "Enter the time when the cooler should be put off. Only available when running in '%s' mode." -#: terrariumTranslations.py:92 +#: terrariumTranslations.py:96 msgid "Select the power switches that should be toggled on the selected times above. Normally these are the switches connected to the cooler. Select all needed switches below." msgstr "Select the power switches that should be toggled on the selected times above. Normally these are the switches connected to the cooler. Select all needed switches below." -#: terrariumTranslations.py:96 +#: terrariumTranslations.py:100 msgid "Choose your interface language." msgstr "Choose your interface language." -#: terrariumTranslations.py:97 +#: terrariumTranslations.py:101 msgid "Holds the username which can make changes (Administrator)." msgstr "Holds the username which can make changes (Administrator)." -#: terrariumTranslations.py:98 +#: terrariumTranslations.py:102 msgid "Enter the new password for the administration user. Leaving empty will not change the password!" msgstr "Enter the new password for the administration user. Leaving empty will not change the password!" -#: terrariumTranslations.py:99 +#: terrariumTranslations.py:103 msgid "Enter the current password in order to change the password." msgstr "Enter the current password in order to change the password." -#: terrariumTranslations.py:100 +#: terrariumTranslations.py:104 msgid "Holds the amount of power in Wattage that the Raspberry PI uses including all USB equipment." msgstr "Holds the amount of power in Wattage that the Raspberry PI uses including all USB equipment." -#: terrariumTranslations.py:101 +#: terrariumTranslations.py:105 msgid "Holds the amount of euro/dollar per 1 kW/h (1 Kilowatt per hour)." msgstr "Holds the amount of euro/dollar per 1 kW/h (1 Kilowatt per hour)." -#: terrariumTranslations.py:102 +#: terrariumTranslations.py:106 msgid "Holds the amount of euro/dollar per 1000 liters water." msgstr "Holds the amount of euro/dollar per 1000 liters water." -#: terrariumTranslations.py:103 +#: terrariumTranslations.py:107 msgid "Choose the temperature indicator. The software will recalculate to the chosen indicator." msgstr "Choose the temperature indicator. The software will recalculate to the chosen indicator." -#: terrariumTranslations.py:104 +#: terrariumTranslations.py:108 msgid "Holds the host name or IP address on which the software will listen for connections. Enter :: for all addresses to bind." msgstr "Holds the host name or IP address on which the software will listen for connections. Enter :: for all addresses to bind." -#: terrariumTranslations.py:105 +#: terrariumTranslations.py:109 msgid "Holds the port number on which the software is listening for connections." msgstr "Holds the port number on which the software is listening for connections." -#: terrariumTranslations.py:106 +#: terrariumTranslations.py:110 msgid "Holds the port number on which the OWFS software is running. Leave empty to disable OWFS support." msgstr "Holds the port number on which the OWFS software is running. Leave empty to disable OWFS support." -#: terrariumTranslations.py:110 +#: terrariumTranslations.py:114 msgid "Holds the name of the animal." msgstr "Holds the name of the animal." -#: terrariumTranslations.py:111 +#: terrariumTranslations.py:115 msgid "Holds the type of the animal" msgstr "Holds the type of the animal" -#: terrariumTranslations.py:112 +#: terrariumTranslations.py:116 msgid "Holds the gender of the animal." msgstr "Holds the gender of the animal." -#: terrariumTranslations.py:113 +#: terrariumTranslations.py:117 msgid "Holds the day of birth of the animal." msgstr "Holds the day of birth of the animal." -#: terrariumTranslations.py:114 +#: terrariumTranslations.py:118 msgid "Holds the species name of the animal." msgstr "Holds the species name of the animal." -#: terrariumTranslations.py:115 +#: terrariumTranslations.py:119 msgid "Holds the latin name of the animal." msgstr "Holds the latin name of the animal." -#: terrariumTranslations.py:116 +#: terrariumTranslations.py:120 msgid "Holds a small description about the animal." msgstr "Holds a small description about the animal." -#: terrariumTranslations.py:117 +#: terrariumTranslations.py:121 msgid "Holds a link to more information." msgstr "Holds a link to more information." -#: terrariumWebserver.py:78 terrariumWebserver.py:84 terrariumWebserver.py:92 +#: terrariumWebserver.py:78 terrariumWebserver.py:84 terrariumWebserver.py:90 +#: terrariumWebserver.py:98 msgid "Authenticate to make any changes" msgstr "Authenticate to make any changes" -#: terrariumWebserver.py:78 terrariumWebserver.py:84 terrariumWebserver.py:92 +#: terrariumWebserver.py:78 terrariumWebserver.py:84 terrariumWebserver.py:90 +#: terrariumWebserver.py:98 msgid "Authentication" msgstr "Authentication" -#: terrariumWebserver.py:78 terrariumWebserver.py:84 terrariumWebserver.py:92 -#: views/system_settings.tpl:23 +#: terrariumWebserver.py:78 terrariumWebserver.py:84 terrariumWebserver.py:90 +#: terrariumWebserver.py:98 views/system_settings.tpl:23 msgid "TerrariumPI" msgstr "TerrariumPI" -#: terrariumWebserver.py:153 +#: terrariumWebserver.py:159 msgid "Data could not be saved" msgstr "Data could not be saved" -#: terrariumWebserver.py:153 +#: terrariumWebserver.py:159 msgid "Error!" msgstr "Error!" -#: terrariumWebserver.py:161 +#: terrariumWebserver.py:167 msgid "Data saved" msgstr "Data saved" -#: terrariumWebserver.py:162 +#: terrariumWebserver.py:168 msgid "Your changes are saved" msgstr "Your changes are saved" -#: terrariumWebserver.py:234 views/inc/menu.tpl:130 +#: terrariumWebserver.py:247 views/inc/menu.tpl:130 msgid "Log out" msgstr "Log out" -#: terrariumWebserver.py:235 +#: terrariumWebserver.py:248 msgid "You are now logged out" msgstr "You are now logged out" @@ -367,13 +385,13 @@ msgstr "Uptime" #: views/dashboard.tpl:24 views/inc/usage_dashboard.tpl:93 #: views/inc/usage_switches.tpl:121 views/inc/usage_switches.tpl:143 -#: views/switch_settings.tpl:28 views/switch_settings.tpl:97 +#: views/switch_settings.tpl:28 views/switch_settings.tpl:99 msgid "Power usage in Watt" msgstr "Power usage in Watt" #: views/dashboard.tpl:35 views/inc/usage_dashboard.tpl:99 #: views/inc/usage_switches.tpl:125 views/inc/usage_switches.tpl:146 -#: views/switch_settings.tpl:31 views/switch_settings.tpl:101 +#: views/switch_settings.tpl:31 views/switch_settings.tpl:103 msgid "Water flow in L/m" msgstr "Water flow in L/m" @@ -517,7 +535,7 @@ msgstr "warning" #: views/dashboard.tpl:207 views/dashboard.tpl:259 views/door_status.tpl:16 #: views/inc/sensor_list.tpl:22 views/inc/usage_dashboard.tpl:67 -#: views/inc/usage_dashboard.tpl:180 views/inc/usage_doors.tpl:31 +#: views/inc/usage_dashboard.tpl:181 views/inc/usage_doors.tpl:31 #: views/inc/usage_sensors.tpl:35 views/inc/usage_switches.tpl:31 #: views/inc/usage_webcams.tpl:29 views/switch_status.tpl:17 #: views/system_status.tpl:15 views/system_status.tpl:71 @@ -526,7 +544,7 @@ msgid "Day" msgstr "Day" #: views/dashboard.tpl:210 views/dashboard.tpl:262 views/door_status.tpl:19 -#: views/inc/sensor_list.tpl:25 views/inc/usage_dashboard.tpl:181 +#: views/inc/sensor_list.tpl:25 views/inc/usage_dashboard.tpl:182 #: views/inc/usage_doors.tpl:34 views/inc/usage_sensors.tpl:38 #: views/inc/usage_switches.tpl:34 views/inc/usage_webcams.tpl:32 #: views/switch_status.tpl:20 views/system_status.tpl:18 @@ -536,7 +554,7 @@ msgid "Week" msgstr "Week" #: views/dashboard.tpl:213 views/dashboard.tpl:265 views/door_status.tpl:22 -#: views/inc/sensor_list.tpl:28 views/inc/usage_dashboard.tpl:182 +#: views/inc/sensor_list.tpl:28 views/inc/usage_dashboard.tpl:183 #: views/inc/usage_doors.tpl:37 views/inc/usage_sensors.tpl:41 #: views/inc/usage_switches.tpl:37 views/inc/usage_webcams.tpl:35 #: views/switch_status.tpl:23 views/system_status.tpl:21 @@ -546,7 +564,7 @@ msgid "Month" msgstr "Month" #: views/dashboard.tpl:216 views/dashboard.tpl:268 views/door_status.tpl:25 -#: views/inc/sensor_list.tpl:31 views/inc/usage_dashboard.tpl:183 +#: views/inc/sensor_list.tpl:31 views/inc/usage_dashboard.tpl:184 #: views/inc/usage_doors.tpl:40 views/inc/usage_sensors.tpl:44 #: views/inc/usage_switches.tpl:40 views/inc/usage_webcams.tpl:38 #: views/switch_status.tpl:26 views/system_status.tpl:24 @@ -583,7 +601,7 @@ msgstr "Required fields are marked with '%s'." #: views/inc/usage_sensors.tpl:162 views/inc/usage_switches.tpl:103 #: views/inc/usage_switches.tpl:134 views/sensor_settings.tpl:19 #: views/sensor_settings.tpl:87 views/switch_settings.tpl:19 -#: views/switch_settings.tpl:78 +#: views/switch_settings.tpl:79 msgid "Hardware" msgstr "Hardware" @@ -592,7 +610,7 @@ msgstr "Hardware" #: views/inc/usage_sensors.tpl:120 views/inc/usage_sensors.tpl:165 #: views/inc/usage_switches.tpl:112 views/inc/usage_switches.tpl:137 #: views/sensor_settings.tpl:22 views/sensor_settings.tpl:99 -#: views/switch_settings.tpl:22 views/switch_settings.tpl:88 +#: views/switch_settings.tpl:22 views/switch_settings.tpl:90 msgid "Address" msgstr "Address" @@ -603,7 +621,7 @@ msgstr "Address" #: views/inc/usage_webcams.tpl:104 views/inc/usage_webcams.tpl:134 #: views/profile.tpl:56 views/sensor_settings.tpl:28 #: views/sensor_settings.tpl:113 views/switch_settings.tpl:25 -#: views/switch_settings.tpl:93 views/webcam_settings.tpl:27 +#: views/switch_settings.tpl:95 views/webcam_settings.tpl:27 #: views/webcam_settings.tpl:86 msgid "Name" msgstr "Name" @@ -630,19 +648,19 @@ msgstr "new" #: views/door_settings.tpl:73 views/hardware.tpl:63 views/hardware.tpl:88 #: views/inc/usage_doors.tpl:101 views/inc/usage_switches.tpl:107 -#: views/switch_settings.tpl:82 +#: views/switch_settings.tpl:83 msgid "GPIO" msgstr "GPIO" #: views/door_settings.tpl:93 views/inc/usage_doors.tpl:70 #: views/inc/usage_sensors.tpl:80 views/inc/usage_switches.tpl:70 #: views/inc/usage_webcams.tpl:66 views/sensor_settings.tpl:142 -#: views/switch_settings.tpl:110 views/webcam_settings.tpl:108 +#: views/switch_settings.tpl:131 views/webcam_settings.tpl:108 msgid "Close" msgstr "Close" #: views/door_settings.tpl:94 views/sensor_settings.tpl:143 -#: views/switch_settings.tpl:111 views/webcam_settings.tpl:109 +#: views/switch_settings.tpl:132 views/webcam_settings.tpl:109 msgid "Add" msgstr "Add" @@ -664,7 +682,7 @@ msgstr "Sensors" msgid "Switches" msgstr "Switches" -#: views/hardware.tpl:19 views/hardware.tpl:107 views/inc/menu.tpl:68 +#: views/hardware.tpl:19 views/hardware.tpl:116 views/inc/menu.tpl:68 #: views/usage.tpl:19 msgid "Doors" msgstr "Doors" @@ -758,7 +776,7 @@ msgstr "TerrariumPI software has support for different kind of relay boards. The msgid "For every relay on the board there is need for a dedicated GPIO pin. For a 4 ports relay board there are 4 control GPIO pins and 2 pins for power and ground needed. This makes that it needs 6 GPIO pins in total." msgstr "For every relay on the board there is need for a dedicated GPIO pin. For a 4 ports relay board there are 4 control GPIO pins and 2 pins for power and ground needed. This makes that it needs 6 GPIO pins in total." -#: views/hardware.tpl:90 views/hardware.tpl:99 +#: views/hardware.tpl:90 views/hardware.tpl:99 views/hardware.tpl:108 msgid "The following boards are tested" msgstr "The following boards are tested" @@ -766,7 +784,7 @@ msgstr "The following boards are tested" msgid "VMA400" msgstr "VMA400" -#: views/hardware.tpl:97 views/hardware.tpl:120 +#: views/hardware.tpl:97 views/hardware.tpl:129 msgid "USB" msgstr "USB" @@ -778,29 +796,41 @@ msgstr "The USB relay board does not need GPIO pins. It only needs one USB conne msgid "Denkovi" msgstr "Denkovi" -#: views/hardware.tpl:108 +#: views/hardware.tpl:106 +msgid "PWM" +msgstr "PWM" + +#: views/hardware.tpl:107 +msgid "With PWM controlled boards it is possible to support dimming devices." +msgstr "With PWM controlled boards it is possible to support dimming devices." + +#: views/hardware.tpl:110 +msgid "Nextevo" +msgstr "Nextevo" + +#: views/hardware.tpl:117 msgid "TerrariumPI software has support for magnetic door sensors. Only versions with two wires are supported. Connect one wire to the ground and the other wire to any GPIO pin that is free (except power and ground pins)." msgstr "TerrariumPI software has support for magnetic door sensors. Only versions with two wires are supported. Connect one wire to the ground and the other wire to any GPIO pin that is free (except power and ground pins)." -#: views/hardware.tpl:111 views/inc/menu.tpl:79 views/inc/usage_webcams.tpl:19 +#: views/hardware.tpl:120 views/inc/menu.tpl:79 views/inc/usage_webcams.tpl:19 #: views/usage.tpl:22 views/webcam.tpl:7 views/webcam_settings.tpl:64 msgid "Webcam" msgstr "Webcam" -#: views/hardware.tpl:112 +#: views/hardware.tpl:121 msgid "TerrariumPI software has support for different kind of cameras. The following cameras below are tested with TerrariumPI software." msgstr "TerrariumPI software has support for different kind of cameras. The following cameras below are tested with TerrariumPI software." -#: views/hardware.tpl:115 views/inc/usage_webcams.tpl:128 +#: views/hardware.tpl:124 views/inc/usage_webcams.tpl:128 #: views/webcam_settings.tpl:21 msgid "RPICam" msgstr "RPICam" -#: views/hardware.tpl:116 +#: views/hardware.tpl:125 msgid "There are different Raspberry Pi cameras available. If the camera is Raspberry Pi compatible, it can be used with TerrariumPI software." msgstr "There are different Raspberry Pi cameras available. If the camera is Raspberry Pi compatible, it can be used with TerrariumPI software." -#: views/hardware.tpl:121 +#: views/hardware.tpl:130 msgid "All kind of USB cameras can be used. Enter physical path of the device like /dev/videoX." msgstr "All kind of USB cameras can be used. Enter physical path of the device like /dev/videoX." @@ -828,12 +858,12 @@ msgstr "Profile" msgid "Forecast" msgstr "Forecast" -#: views/inc/menu.tpl:46 views/inc/usage_dashboard.tpl:158 +#: views/inc/menu.tpl:46 views/inc/usage_dashboard.tpl:159 #: views/inc/usage_sensors.tpl:128 views/sensor_settings.tpl:107 msgid "Temperature" msgstr "Temperature" -#: views/inc/menu.tpl:49 views/inc/usage_dashboard.tpl:157 +#: views/inc/menu.tpl:49 views/inc/usage_dashboard.tpl:158 #: views/inc/usage_sensors.tpl:129 views/sensor_settings.tpl:108 msgid "Humidity" msgstr "Humidity" @@ -1418,7 +1448,7 @@ msgstr "The history graph will shows when a switch was powered on. It will show msgid "On the switch settings page you can configure all needed switches. Click on %s button to add a new switch. And empty form like below is shown and has to be filled in. Make sure the right values are filled in. All fields with a %s are required." msgstr "On the switch settings page you can configure all needed switches. Click on %s button to add a new switch. And empty form like below is shown and has to be filled in. Make sure the right values are filled in. All fields with a %s are required." -#: views/inc/usage_switches.tpl:106 views/switch_settings.tpl:81 +#: views/inc/usage_switches.tpl:106 views/switch_settings.tpl:82 msgid "FTDI" msgstr "FTDI" @@ -1689,10 +1719,30 @@ msgstr "Here you can configure your power switches." msgid "Add new switch" msgstr "Add new switch" -#: views/switch_settings.tpl:83 +#: views/switch_settings.tpl:84 msgid "GPIO Inverse" msgstr "GPIO Inverse" +#: views/switch_settings.tpl:85 +msgid "PWM Dimmer" +msgstr "PWM Dimmer" + +#: views/switch_settings.tpl:109 +msgid "Dimmer on duration" +msgstr "Dimmer on duration" + +#: views/switch_settings.tpl:113 +msgid "Dimmer on percentage" +msgstr "Dimmer on percentage" + +#: views/switch_settings.tpl:117 +msgid "Dimmer off duration" +msgstr "Dimmer off duration" + +#: views/switch_settings.tpl:121 +msgid "Dimmer off percentage" +msgstr "Dimmer off percentage" + #: views/system_environment.tpl:16 msgid "Here you can configure your environment." msgstr "Here you can configure your environment." @@ -1981,6 +2031,10 @@ msgstr "Total memory" msgid "Total open for" msgstr "Total open for" +#: Missing text string +msgid "Universal AC MAINS Dimmer - MPDMv4.1" +msgstr "Universal AC MAINS Dimmer - MPDMv4.1" + #: Missing text string msgid "USB Relay board" msgstr "USB Relay board" diff --git a/locales/terrariumpi.pot b/locales/terrariumpi.pot index 18ec969b0..4ecc1b1f9 100644 --- a/locales/terrariumpi.pot +++ b/locales/terrariumpi.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2017-09-27 20:19+CEST\n" +"POT-Creation-Date: 2017-10-21 17:42+CEST\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -79,55 +79,71 @@ msgstr "" msgid "Holds the switch water flow in liters per minute when switched on" msgstr "" +#: terrariumTranslations.py:43 +msgid "Holds the amount of seconds for the duration in which it increases the power." +msgstr "" + +#: terrariumTranslations.py:44 +msgid "Holds the amount in percentage to go to when switched on." +msgstr "" + +#: terrariumTranslations.py:45 +msgid "Holds the amount of seconds for the duration in which it decresses the power." +msgstr "" + #: terrariumTranslations.py:46 +msgid "Holds the amount in percentage to go to when switched off." +msgstr "" + +#: terrariumTranslations.py:50 msgid "Holds the door hardware type. Supported hardware types are: %s." msgstr "" -#: terrariumTranslations.py:47 +#: terrariumTranslations.py:51 msgid "Holds the door address. When using GPIO use Physical GPIO pin numbering as address." msgstr "" -#: terrariumTranslations.py:48 +#: terrariumTranslations.py:52 msgid "Holds the door name." msgstr "" -#: terrariumTranslations.py:52 +#: terrariumTranslations.py:56 msgid "Holds the webcam location source. Supported sources are: %s" msgstr "" -#: terrariumTranslations.py:53 +#: terrariumTranslations.py:57 msgid "Holds the webcam name." msgstr "" -#: terrariumTranslations.py:54 +#: terrariumTranslations.py:58 msgid "Holds the webcam rotation of the image." msgstr "" -#: terrariumTranslations.py:55 +#: terrariumTranslations.py:59 msgid "Shows the webcam preview image." msgstr "" -#: terrariumTranslations.py:59 +#: terrariumTranslations.py:63 msgid "Enable or disable the light system. When enabled, you can make changes below. By disabling it will not loose the current settings. It will temporary stop the lighting system." msgstr "" -#: terrariumTranslations.py:60 +#: terrariumTranslations.py:64 msgid "Select the mode on which the lights will be put on and off. Select '%s' to use the sun rise and sun set at your location. This will make the amount of lighting variable to the actual amount of daylight. When selecting '%s', the light will put on and off at selected times." msgstr "" -#: terrariumTranslations.py:60 terrariumTranslations.py:61 -#: terrariumTranslations.py:62 terrariumTranslations.py:80 -#: terrariumTranslations.py:81 terrariumTranslations.py:82 -#: terrariumTranslations.py:89 terrariumTranslations.py:90 -#: terrariumTranslations.py:91 views/inc/usage_environment.tpl:33 +#: terrariumTranslations.py:64 terrariumTranslations.py:65 +#: terrariumTranslations.py:66 terrariumTranslations.py:84 +#: terrariumTranslations.py:85 terrariumTranslations.py:86 +#: terrariumTranslations.py:93 terrariumTranslations.py:94 +#: terrariumTranslations.py:95 views/inc/usage_environment.tpl:33 #: views/inc/usage_environment.tpl:229 views/inc/usage_environment.tpl:323 #: views/system_environment.tpl:46 views/system_environment.tpl:202 #: views/system_environment.tpl:277 msgid "Timer" msgstr "" -#: terrariumTranslations.py:60 terrariumTranslations.py:65 -#: terrariumTranslations.py:80 terrariumTranslations.py:89 +#: terrariumTranslations.py:64 terrariumTranslations.py:69 +#: terrariumTranslations.py:84 terrariumTranslations.py:93 #: views/inc/menu.tpl:32 views/inc/usage_environment.tpl:34 #: views/inc/usage_environment.tpl:230 views/inc/usage_environment.tpl:324 #: views/inc/usage_weather.tpl:13 views/inc/usage_weather.tpl:82 @@ -138,71 +154,71 @@ msgstr "" msgid "Weather" msgstr "" -#: terrariumTranslations.py:61 +#: terrariumTranslations.py:65 msgid "Enter the time when the light should be put on. Only available when running in '%s' mode." msgstr "" -#: terrariumTranslations.py:62 +#: terrariumTranslations.py:66 msgid "Enter the time when the lights should be put off. Only available when running in '%s' mode." msgstr "" -#: terrariumTranslations.py:63 +#: terrariumTranslations.py:67 msgid "Enter the maximum amount of lights time in hours. When the time between on and off is more then this maximum, the on and off time will be shifted more to each other." msgstr "" -#: terrariumTranslations.py:64 +#: terrariumTranslations.py:68 msgid "Enter the minimum amount of lights time in hours. When the time between on and off is less then this amount of hours, the on and off time will be widened until the minimum amount of hours entered here." msgstr "" -#: terrariumTranslations.py:65 +#: terrariumTranslations.py:69 msgid "Enter the amount of hours that the lights should shift. Is only needed when running in the '%s' mode. Enter a positive number for adding hours to the start time. Use negative numbers for subtracting from the start time." msgstr "" -#: terrariumTranslations.py:66 +#: terrariumTranslations.py:70 msgid "Select the power switches that should be toggled on the selected times above. Normally these are the switches connected to the lights. Select all needed switches below." msgstr "" -#: terrariumTranslations.py:69 +#: terrariumTranslations.py:73 msgid "Enable or disable the sprayer system. When enabled, you can make changes below. By disabling it will not loose the current settings. It will temporary stop the sprayer system." msgstr "" -#: terrariumTranslations.py:70 +#: terrariumTranslations.py:74 msgid "Enable spraying when the lights are off. This can cause water flow when there is not enough heat to vaporize the water." msgstr "" -#: terrariumTranslations.py:71 +#: terrariumTranslations.py:75 msgid "Select the operating mode. For now only sensor mode is available." msgstr "" -#: terrariumTranslations.py:72 +#: terrariumTranslations.py:76 msgid "How much time must there be between two spray actions and after start up. Enter the amount of seconds in which the humidity can settle." msgstr "" -#: terrariumTranslations.py:73 +#: terrariumTranslations.py:77 msgid "How long is the system spraying. Enter the amount of seconds that the system is on when the humidity is too low." msgstr "" -#: terrariumTranslations.py:74 +#: terrariumTranslations.py:78 msgid "Select the power switches that should be toggled on the selected times above. Normally these are the switches connected to the sprayer. Select all needed switches below." msgstr "" -#: terrariumTranslations.py:75 +#: terrariumTranslations.py:79 msgid "Select the humidity sensors that are used to control the humidity. When selecting multiple sensors, the average is calculated to determine the final humidity." msgstr "" -#: terrariumTranslations.py:78 +#: terrariumTranslations.py:82 msgid "Enable or disable the heater system. When enabled, you can make changes below. By disabling it will not loose the current settings. It will temporary stop the heater system." msgstr "" -#: terrariumTranslations.py:79 +#: terrariumTranslations.py:83 msgid "Enable heating when the lights are on. This can cause overheating when the lights are on." msgstr "" -#: terrariumTranslations.py:80 terrariumTranslations.py:89 +#: terrariumTranslations.py:84 terrariumTranslations.py:93 msgid "Select the operating mode. Use '%s' mode to select the time period in which the heating is running. Select '%s' mode to use the sun rise and sun set as on and off times. When the sun rises the heating system will stop. Use '%s' mode to have the heating running when the lights are off." msgstr "" -#: terrariumTranslations.py:80 terrariumTranslations.py:89 +#: terrariumTranslations.py:84 terrariumTranslations.py:93 #: views/inc/usage_environment.tpl:137 views/inc/usage_environment.tpl:231 #: views/inc/usage_environment.tpl:325 views/inc/usage_sensors.tpl:23 #: views/sensor_settings.tpl:74 views/system_environment.tpl:129 @@ -210,152 +226,154 @@ msgstr "" msgid "Sensor" msgstr "" -#: terrariumTranslations.py:81 +#: terrariumTranslations.py:85 msgid "Enter the time when the heater should be put on. Only available when running in '%s' mode." msgstr "" -#: terrariumTranslations.py:82 +#: terrariumTranslations.py:86 msgid "Enter the time when the heater should be put off. Only available when running in '%s' mode." msgstr "" -#: terrariumTranslations.py:83 +#: terrariumTranslations.py:87 msgid "Select the power switches that should be toggled on the selected times above. Normally these are the switches connected to the heater. Select all needed switches below." msgstr "" -#: terrariumTranslations.py:84 terrariumTranslations.py:93 +#: terrariumTranslations.py:88 terrariumTranslations.py:97 msgid "Select the temperature sensors that are used to control the temperature. When selecting multiple sensors, the average is calculated to determine the final temperature." msgstr "" -#: terrariumTranslations.py:87 +#: terrariumTranslations.py:91 msgid "Enable or disable the cooler system. When enabled, you can make changes below. By disabling it will not loose the current settings. It will temporary stop the cooler system." msgstr "" -#: terrariumTranslations.py:88 +#: terrariumTranslations.py:92 msgid "Enable cooling when the lights are off. This can cause a very low temperature when the lights are off." msgstr "" -#: terrariumTranslations.py:90 +#: terrariumTranslations.py:94 msgid "Enter the time when the cooler should be put on. Only available when running in '%s' mode." msgstr "" -#: terrariumTranslations.py:91 +#: terrariumTranslations.py:95 msgid "Enter the time when the cooler should be put off. Only available when running in '%s' mode." msgstr "" -#: terrariumTranslations.py:92 +#: terrariumTranslations.py:96 msgid "Select the power switches that should be toggled on the selected times above. Normally these are the switches connected to the cooler. Select all needed switches below." msgstr "" -#: terrariumTranslations.py:96 +#: terrariumTranslations.py:100 msgid "Choose your interface language." msgstr "" -#: terrariumTranslations.py:97 +#: terrariumTranslations.py:101 msgid "Holds the username which can make changes (Administrator)." msgstr "" -#: terrariumTranslations.py:98 +#: terrariumTranslations.py:102 msgid "Enter the new password for the administration user. Leaving empty will not change the password!" msgstr "" -#: terrariumTranslations.py:99 +#: terrariumTranslations.py:103 msgid "Enter the current password in order to change the password." msgstr "" -#: terrariumTranslations.py:100 +#: terrariumTranslations.py:104 msgid "Holds the amount of power in Wattage that the Raspberry PI uses including all USB equipment." msgstr "" -#: terrariumTranslations.py:101 +#: terrariumTranslations.py:105 msgid "Holds the amount of euro/dollar per 1 kW/h (1 Kilowatt per hour)." msgstr "" -#: terrariumTranslations.py:102 +#: terrariumTranslations.py:106 msgid "Holds the amount of euro/dollar per 1000 liters water." msgstr "" -#: terrariumTranslations.py:103 +#: terrariumTranslations.py:107 msgid "Choose the temperature indicator. The software will recalculate to the chosen indicator." msgstr "" -#: terrariumTranslations.py:104 +#: terrariumTranslations.py:108 msgid "Holds the host name or IP address on which the software will listen for connections. Enter :: for all addresses to bind." msgstr "" -#: terrariumTranslations.py:105 +#: terrariumTranslations.py:109 msgid "Holds the port number on which the software is listening for connections." msgstr "" -#: terrariumTranslations.py:106 +#: terrariumTranslations.py:110 msgid "Holds the port number on which the OWFS software is running. Leave empty to disable OWFS support." msgstr "" -#: terrariumTranslations.py:110 +#: terrariumTranslations.py:114 msgid "Holds the name of the animal." msgstr "" -#: terrariumTranslations.py:111 +#: terrariumTranslations.py:115 msgid "Holds the type of the animal" msgstr "" -#: terrariumTranslations.py:112 +#: terrariumTranslations.py:116 msgid "Holds the gender of the animal." msgstr "" -#: terrariumTranslations.py:113 +#: terrariumTranslations.py:117 msgid "Holds the day of birth of the animal." msgstr "" -#: terrariumTranslations.py:114 +#: terrariumTranslations.py:118 msgid "Holds the species name of the animal." msgstr "" -#: terrariumTranslations.py:115 +#: terrariumTranslations.py:119 msgid "Holds the latin name of the animal." msgstr "" -#: terrariumTranslations.py:116 +#: terrariumTranslations.py:120 msgid "Holds a small description about the animal." msgstr "" -#: terrariumTranslations.py:117 +#: terrariumTranslations.py:121 msgid "Holds a link to more information." msgstr "" -#: terrariumWebserver.py:78 terrariumWebserver.py:84 terrariumWebserver.py:92 +#: terrariumWebserver.py:78 terrariumWebserver.py:84 terrariumWebserver.py:90 +#: terrariumWebserver.py:98 msgid "Authenticate to make any changes" msgstr "" -#: terrariumWebserver.py:78 terrariumWebserver.py:84 terrariumWebserver.py:92 +#: terrariumWebserver.py:78 terrariumWebserver.py:84 terrariumWebserver.py:90 +#: terrariumWebserver.py:98 msgid "Authentication" msgstr "" -#: terrariumWebserver.py:78 terrariumWebserver.py:84 terrariumWebserver.py:92 -#: views/system_settings.tpl:23 +#: terrariumWebserver.py:78 terrariumWebserver.py:84 terrariumWebserver.py:90 +#: terrariumWebserver.py:98 views/system_settings.tpl:23 msgid "TerrariumPI" msgstr "" -#: terrariumWebserver.py:153 +#: terrariumWebserver.py:159 msgid "Data could not be saved" msgstr "" -#: terrariumWebserver.py:153 +#: terrariumWebserver.py:159 msgid "Error!" msgstr "" -#: terrariumWebserver.py:161 +#: terrariumWebserver.py:167 msgid "Data saved" msgstr "" -#: terrariumWebserver.py:162 +#: terrariumWebserver.py:168 msgid "Your changes are saved" msgstr "" -#: terrariumWebserver.py:234 views/inc/menu.tpl:130 +#: terrariumWebserver.py:247 views/inc/menu.tpl:130 msgid "Log out" msgstr "" -#: terrariumWebserver.py:235 +#: terrariumWebserver.py:248 msgid "You are now logged out" msgstr "" @@ -366,13 +384,13 @@ msgstr "" #: views/dashboard.tpl:24 views/inc/usage_dashboard.tpl:93 #: views/inc/usage_switches.tpl:121 views/inc/usage_switches.tpl:143 -#: views/switch_settings.tpl:28 views/switch_settings.tpl:97 +#: views/switch_settings.tpl:28 views/switch_settings.tpl:99 msgid "Power usage in Watt" msgstr "" #: views/dashboard.tpl:35 views/inc/usage_dashboard.tpl:99 #: views/inc/usage_switches.tpl:125 views/inc/usage_switches.tpl:146 -#: views/switch_settings.tpl:31 views/switch_settings.tpl:101 +#: views/switch_settings.tpl:31 views/switch_settings.tpl:103 msgid "Water flow in L/m" msgstr "" @@ -516,7 +534,7 @@ msgstr "" #: views/dashboard.tpl:207 views/dashboard.tpl:259 views/door_status.tpl:16 #: views/inc/sensor_list.tpl:22 views/inc/usage_dashboard.tpl:67 -#: views/inc/usage_dashboard.tpl:180 views/inc/usage_doors.tpl:31 +#: views/inc/usage_dashboard.tpl:181 views/inc/usage_doors.tpl:31 #: views/inc/usage_sensors.tpl:35 views/inc/usage_switches.tpl:31 #: views/inc/usage_webcams.tpl:29 views/switch_status.tpl:17 #: views/system_status.tpl:15 views/system_status.tpl:71 @@ -525,7 +543,7 @@ msgid "Day" msgstr "" #: views/dashboard.tpl:210 views/dashboard.tpl:262 views/door_status.tpl:19 -#: views/inc/sensor_list.tpl:25 views/inc/usage_dashboard.tpl:181 +#: views/inc/sensor_list.tpl:25 views/inc/usage_dashboard.tpl:182 #: views/inc/usage_doors.tpl:34 views/inc/usage_sensors.tpl:38 #: views/inc/usage_switches.tpl:34 views/inc/usage_webcams.tpl:32 #: views/switch_status.tpl:20 views/system_status.tpl:18 @@ -535,7 +553,7 @@ msgid "Week" msgstr "" #: views/dashboard.tpl:213 views/dashboard.tpl:265 views/door_status.tpl:22 -#: views/inc/sensor_list.tpl:28 views/inc/usage_dashboard.tpl:182 +#: views/inc/sensor_list.tpl:28 views/inc/usage_dashboard.tpl:183 #: views/inc/usage_doors.tpl:37 views/inc/usage_sensors.tpl:41 #: views/inc/usage_switches.tpl:37 views/inc/usage_webcams.tpl:35 #: views/switch_status.tpl:23 views/system_status.tpl:21 @@ -545,7 +563,7 @@ msgid "Month" msgstr "" #: views/dashboard.tpl:216 views/dashboard.tpl:268 views/door_status.tpl:25 -#: views/inc/sensor_list.tpl:31 views/inc/usage_dashboard.tpl:183 +#: views/inc/sensor_list.tpl:31 views/inc/usage_dashboard.tpl:184 #: views/inc/usage_doors.tpl:40 views/inc/usage_sensors.tpl:44 #: views/inc/usage_switches.tpl:40 views/inc/usage_webcams.tpl:38 #: views/switch_status.tpl:26 views/system_status.tpl:24 @@ -582,7 +600,7 @@ msgstr "" #: views/inc/usage_sensors.tpl:162 views/inc/usage_switches.tpl:103 #: views/inc/usage_switches.tpl:134 views/sensor_settings.tpl:19 #: views/sensor_settings.tpl:87 views/switch_settings.tpl:19 -#: views/switch_settings.tpl:78 +#: views/switch_settings.tpl:79 msgid "Hardware" msgstr "" @@ -591,7 +609,7 @@ msgstr "" #: views/inc/usage_sensors.tpl:120 views/inc/usage_sensors.tpl:165 #: views/inc/usage_switches.tpl:112 views/inc/usage_switches.tpl:137 #: views/sensor_settings.tpl:22 views/sensor_settings.tpl:99 -#: views/switch_settings.tpl:22 views/switch_settings.tpl:88 +#: views/switch_settings.tpl:22 views/switch_settings.tpl:90 msgid "Address" msgstr "" @@ -602,7 +620,7 @@ msgstr "" #: views/inc/usage_webcams.tpl:104 views/inc/usage_webcams.tpl:134 #: views/profile.tpl:56 views/sensor_settings.tpl:28 #: views/sensor_settings.tpl:113 views/switch_settings.tpl:25 -#: views/switch_settings.tpl:93 views/webcam_settings.tpl:27 +#: views/switch_settings.tpl:95 views/webcam_settings.tpl:27 #: views/webcam_settings.tpl:86 msgid "Name" msgstr "" @@ -629,19 +647,19 @@ msgstr "" #: views/door_settings.tpl:73 views/hardware.tpl:63 views/hardware.tpl:88 #: views/inc/usage_doors.tpl:101 views/inc/usage_switches.tpl:107 -#: views/switch_settings.tpl:82 +#: views/switch_settings.tpl:83 msgid "GPIO" msgstr "" #: views/door_settings.tpl:93 views/inc/usage_doors.tpl:70 #: views/inc/usage_sensors.tpl:80 views/inc/usage_switches.tpl:70 #: views/inc/usage_webcams.tpl:66 views/sensor_settings.tpl:142 -#: views/switch_settings.tpl:110 views/webcam_settings.tpl:108 +#: views/switch_settings.tpl:131 views/webcam_settings.tpl:108 msgid "Close" msgstr "" #: views/door_settings.tpl:94 views/sensor_settings.tpl:143 -#: views/switch_settings.tpl:111 views/webcam_settings.tpl:109 +#: views/switch_settings.tpl:132 views/webcam_settings.tpl:109 msgid "Add" msgstr "" @@ -663,7 +681,7 @@ msgstr "" msgid "Switches" msgstr "" -#: views/hardware.tpl:19 views/hardware.tpl:107 views/inc/menu.tpl:68 +#: views/hardware.tpl:19 views/hardware.tpl:116 views/inc/menu.tpl:68 #: views/usage.tpl:19 msgid "Doors" msgstr "" @@ -757,7 +775,7 @@ msgstr "" msgid "For every relay on the board there is need for a dedicated GPIO pin. For a 4 ports relay board there are 4 control GPIO pins and 2 pins for power and ground needed. This makes that it needs 6 GPIO pins in total." msgstr "" -#: views/hardware.tpl:90 views/hardware.tpl:99 +#: views/hardware.tpl:90 views/hardware.tpl:99 views/hardware.tpl:108 msgid "The following boards are tested" msgstr "" @@ -765,7 +783,7 @@ msgstr "" msgid "VMA400" msgstr "" -#: views/hardware.tpl:97 views/hardware.tpl:120 +#: views/hardware.tpl:97 views/hardware.tpl:129 msgid "USB" msgstr "" @@ -777,29 +795,41 @@ msgstr "" msgid "Denkovi" msgstr "" -#: views/hardware.tpl:108 +#: views/hardware.tpl:106 +msgid "PWM" +msgstr "" + +#: views/hardware.tpl:107 +msgid "With PWM controlled boards it is possible to support dimming devices." +msgstr "" + +#: views/hardware.tpl:110 +msgid "Nextevo" +msgstr "" + +#: views/hardware.tpl:117 msgid "TerrariumPI software has support for magnetic door sensors. Only versions with two wires are supported. Connect one wire to the ground and the other wire to any GPIO pin that is free (except power and ground pins)." msgstr "" -#: views/hardware.tpl:111 views/inc/menu.tpl:79 views/inc/usage_webcams.tpl:19 +#: views/hardware.tpl:120 views/inc/menu.tpl:79 views/inc/usage_webcams.tpl:19 #: views/usage.tpl:22 views/webcam.tpl:7 views/webcam_settings.tpl:64 msgid "Webcam" msgstr "" -#: views/hardware.tpl:112 +#: views/hardware.tpl:121 msgid "TerrariumPI software has support for different kind of cameras. The following cameras below are tested with TerrariumPI software." msgstr "" -#: views/hardware.tpl:115 views/inc/usage_webcams.tpl:128 +#: views/hardware.tpl:124 views/inc/usage_webcams.tpl:128 #: views/webcam_settings.tpl:21 msgid "RPICam" msgstr "" -#: views/hardware.tpl:116 +#: views/hardware.tpl:125 msgid "There are different Raspberry Pi cameras available. If the camera is Raspberry Pi compatible, it can be used with TerrariumPI software." msgstr "" -#: views/hardware.tpl:121 +#: views/hardware.tpl:130 msgid "All kind of USB cameras can be used. Enter physical path of the device like /dev/videoX." msgstr "" @@ -827,12 +857,12 @@ msgstr "" msgid "Forecast" msgstr "" -#: views/inc/menu.tpl:46 views/inc/usage_dashboard.tpl:158 +#: views/inc/menu.tpl:46 views/inc/usage_dashboard.tpl:159 #: views/inc/usage_sensors.tpl:128 views/sensor_settings.tpl:107 msgid "Temperature" msgstr "" -#: views/inc/menu.tpl:49 views/inc/usage_dashboard.tpl:157 +#: views/inc/menu.tpl:49 views/inc/usage_dashboard.tpl:158 #: views/inc/usage_sensors.tpl:129 views/sensor_settings.tpl:108 msgid "Humidity" msgstr "" @@ -1417,7 +1447,7 @@ msgstr "" msgid "On the switch settings page you can configure all needed switches. Click on %s button to add a new switch. And empty form like below is shown and has to be filled in. Make sure the right values are filled in. All fields with a %s are required." msgstr "" -#: views/inc/usage_switches.tpl:106 views/switch_settings.tpl:81 +#: views/inc/usage_switches.tpl:106 views/switch_settings.tpl:82 msgid "FTDI" msgstr "" @@ -1688,10 +1718,30 @@ msgstr "" msgid "Add new switch" msgstr "" -#: views/switch_settings.tpl:83 +#: views/switch_settings.tpl:84 msgid "GPIO Inverse" msgstr "" +#: views/switch_settings.tpl:85 +msgid "PWM Dimmer" +msgstr "" + +#: views/switch_settings.tpl:109 +msgid "Dimmer on duration" +msgstr "" + +#: views/switch_settings.tpl:113 +msgid "Dimmer on percentage" +msgstr "" + +#: views/switch_settings.tpl:117 +msgid "Dimmer off duration" +msgstr "" + +#: views/switch_settings.tpl:121 +msgid "Dimmer off percentage" +msgstr "" + #: views/system_environment.tpl:16 msgid "Here you can configure your environment." msgstr "" @@ -1980,6 +2030,10 @@ msgstr "" msgid "Total open for" msgstr "" +#: Missing text string +msgid "Universal AC MAINS Dimmer - MPDMv4.1" +msgstr "" + #: Missing text string msgid "USB Relay board" msgstr "" diff --git a/terrariumTranslations.py b/terrariumTranslations.py index 1d563f525..37830595a 100644 --- a/terrariumTranslations.py +++ b/terrariumTranslations.py @@ -40,6 +40,10 @@ def __load(self): self.translations['switch_field_name'] = _('Holds the switch name.') self.translations['switch_field_power_wattage'] = _('Holds the switch power usage in Watt when switched on.') self.translations['switch_field_water_flow'] = _('Holds the switch water flow in liters per minute when switched on') + self.translations['switch_field_dimmer_on_duration'] = _('Holds the amount of seconds for the duration in which it increases the power.') + self.translations['switch_field_dimmer_on_percentage'] = _('Holds the amount in percentage to go to when switched on.') + self.translations['switch_field_dimmer_off_duration'] = _('Holds the amount of seconds for the duration in which it decresses the power.') + self.translations['switch_field_dimmer_off_percentage'] = _('Holds the amount in percentage to go to when switched off.') # End switches # Doors From 458f4d2a58c465b329a4e00923b6fcf519382d82 Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sat, 21 Oct 2017 19:29:50 +0200 Subject: [PATCH 08/34] Fix switch toggle to support dimmers --- terrariumSwitch.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/terrariumSwitch.py b/terrariumSwitch.py index b5301a712..2471966b8 100644 --- a/terrariumSwitch.py +++ b/terrariumSwitch.py @@ -271,29 +271,25 @@ def set_water_flow(self,value): def toggle(self): if self.get_state() is not None: - old_state = self.get_state() - self.set_state(not old_state) - return self.get_state() is not old_state + if self.is_on(): + self.off() + else: + self.on() + return True return None def is_on(self): - state = None if self.get_hardware_type() == 'pwm-dimmer': - state = self.get_state() > 0.0 + return self.get_state() > self.get_dimmer_off_percentage() else: - state = self.get_state() - - return state is terrariumSwitch.ON + return self.get_state() is terrariumSwitch.ON def is_off(self): - state = None if self.get_hardware_type() == 'pwm-dimmer': - state = self.get_state() == 0.0 + return self.get_state() == self.get_dimmer_off_percentage() else: - state = self.get_state() - - return state is terrariumSwitch.OFF + return self.get_state() is terrariumSwitch.OFF def on(self): if self.get_state() is None or self.is_off(): From 1ca704118f5db21aeb96bb5a05016264a88b9cfa Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sat, 21 Oct 2017 19:47:03 +0200 Subject: [PATCH 09/34] Fix switch toggle to support dimmers --- static/js/terrariumpi.js | 3 +++ terrariumSwitch.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/static/js/terrariumpi.js b/static/js/terrariumpi.js index 96aa96879..0379c25b3 100644 --- a/static/js/terrariumpi.js +++ b/static/js/terrariumpi.js @@ -1245,6 +1245,9 @@ function update_power_switch(id, data) { return value + '%'; } }); + console.log(data); + data.state = data.state >= data.dimmer_off_percentage + console.log(data); } power_switch.find('span.glyphicon').removeClass('blue green').addClass((data.state ? 'green' : 'blue')); } diff --git a/terrariumSwitch.py b/terrariumSwitch.py index 2471966b8..5392bdd31 100644 --- a/terrariumSwitch.py +++ b/terrariumSwitch.py @@ -175,6 +175,8 @@ def set_state(self, state, force = False): thread.start_new_thread(self.__dim_switch, (self.state,state)) + print 'New state' + print state self.state = state if self.get_hardware_type() != 'pwm-dimmer': logger.info('Toggle switch \'%s\' from %s',self.get_name(),('off to on' if self.is_on() else 'on to off')) From e4f5cd4686a9b0cfe70c2d1aa923c609fa93e7ae Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sat, 21 Oct 2017 20:01:56 +0200 Subject: [PATCH 10/34] Better on and off detection for dimmers --- static/js/terrariumpi.js | 8 +++----- terrariumSwitch.py | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/static/js/terrariumpi.js b/static/js/terrariumpi.js index 0379c25b3..d22ef74b3 100644 --- a/static/js/terrariumpi.js +++ b/static/js/terrariumpi.js @@ -1234,20 +1234,18 @@ function update_power_switch(id, data) { power_switch.find('h2 small.data_update').text(update_data); if (data.hardwaretype === 'pwm-dimmer') { - power_switch.find('div.power_switch').removeClass('big').addClass('dimmer').html(''); + power_switch.find('div.power_switch').removeClass('big').addClass('dimmer').html(''); power_switch.find('.knob').knob({ release: function(value) { - $.getJSON('/api/switch/state/' + id + '/' + value,function(data){ + $.getJSON('/api/switch/state/' + id + '/' + value,function(dummy){ }); }, format: function(value) { return value + '%'; } }); - console.log(data); - data.state = data.state >= data.dimmer_off_percentage - console.log(data); + data.state = data.state > data.dimmer_off_percentage } power_switch.find('span.glyphicon').removeClass('blue green').addClass((data.state ? 'green' : 'blue')); } diff --git a/terrariumSwitch.py b/terrariumSwitch.py index 5392bdd31..2471966b8 100644 --- a/terrariumSwitch.py +++ b/terrariumSwitch.py @@ -175,8 +175,6 @@ def set_state(self, state, force = False): thread.start_new_thread(self.__dim_switch, (self.state,state)) - print 'New state' - print state self.state = state if self.get_hardware_type() != 'pwm-dimmer': logger.info('Toggle switch \'%s\' from %s',self.get_name(),('off to on' if self.is_on() else 'on to off')) From 771344f7d19711d910ece4964b3690a8b2ad7feb Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sat, 21 Oct 2017 20:10:53 +0200 Subject: [PATCH 11/34] Enable pigpiod service at reboot --- install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/install.sh b/install.sh index 9f3c15f72..82b14e39a 100755 --- a/install.sh +++ b/install.sh @@ -71,6 +71,8 @@ fi groupadd gpio 2> /dev/null usermod -a -G gpio pi 2> /dev/null +systemctl enable pigpiod + sync # We are done! From 318c133d210a01d1d6dcbe20ea53a9cd46ce8cd6 Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sun, 22 Oct 2017 18:46:02 +0200 Subject: [PATCH 12/34] Make PI user restart PiGPIOd process --- install.sh | 2 ++ start.sh | 3 +++ 2 files changed, 5 insertions(+) diff --git a/install.sh b/install.sh index 82b14e39a..41a999a32 100755 --- a/install.sh +++ b/install.sh @@ -71,6 +71,8 @@ fi groupadd gpio 2> /dev/null usermod -a -G gpio pi 2> /dev/null +# Make sure pigpiod is started at boot, and that user PI can restart it with sudo command +echo "pi ALL=(ALL) NOPASSWD: /usr/sbin/service pigpiod restart" > /etc/sudoers.d/terrariumpi systemctl enable pigpiod sync diff --git a/start.sh b/start.sh index 7891977f9..63a889784 100755 --- a/start.sh +++ b/start.sh @@ -53,6 +53,9 @@ then sleep 1 done echo " restart!" + + # Restart PiGPIOd process.... + sudo service pigpiod restart done else WHOAMI=`whoami` From 51babad8cf755690e8e7e77f6f8247edbd603e7b Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sun, 22 Oct 2017 18:47:22 +0200 Subject: [PATCH 13/34] Remove unused settings for non dimmer switches --- terrariumConfig.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/terrariumConfig.py b/terrariumConfig.py index a0c9bb99c..4ca5206df 100644 --- a/terrariumConfig.py +++ b/terrariumConfig.py @@ -297,6 +297,19 @@ def save_power_switch(self,data): if 'current_water_flow' in data: del(data['current_water_flow']) + if data['hardwaretype'] != 'pwm-dimmer': + if 'dimmer_off_duration' in data: + del(data['dimmer_off_duration']) + + if 'dimmer_off_percentage' in data: + del(data['dimmer_off_percentage']) + + if 'dimmer_on_duration' in data: + del(data['dimmer_on_duration']) + + if 'dimmer_on_percentage' in data: + del(data['dimmer_on_percentage']) + return self.__update_config('switch' + str(data['id']),data) def save_power_switches(self,data): From f66cb0499133a3ee69b4f70847b40d3c1c83ddb8 Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sun, 22 Oct 2017 18:49:02 +0200 Subject: [PATCH 14/34] Trying to add more stability for dimming hardware --- terrariumSwitch.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/terrariumSwitch.py b/terrariumSwitch.py index 2471966b8..b492cdca4 100644 --- a/terrariumSwitch.py +++ b/terrariumSwitch.py @@ -20,8 +20,8 @@ class terrariumSwitch(): # PWM Dimmer settings PWM_DIMMER_MAXDIM = 880 # http://www.esp8266-projects.com/2017/04/raspberry-pi-domoticz-ac-dimmer-part-1/ - PWM_DIMMER_MIN_TIMEOUT=0.3 - PWM_DIMMER_MIN_STEP=1.0 + PWM_DIMMER_MIN_TIMEOUT=0.2 + PWM_DIMMER_MIN_STEP=1 bitbang_addresses = { "1":"2", @@ -91,6 +91,7 @@ def __load_pwm_device(self): def __dim_switch(self,from_value,to_value): # When the dimmer is working, ignore new state changes. if not self.__dimmer_running: + self.__pigpio.set_pull_up_down(int(self.get_address()), pigpio.PUD_OFF) self.__dimmer_running = True if from_value is None: From 2c2715693a78c4b547188f4c95184e4938e1e61d Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sun, 22 Oct 2017 18:49:25 +0200 Subject: [PATCH 15/34] Updated data logging and graphing. Reduced load on the client side --- static/js/terrariumpi.js | 167 ++++--------------------- terrariumCollector.py | 264 +++++++++++++++++++-------------------- terrariumEngine.py | 54 ++++---- 3 files changed, 179 insertions(+), 306 deletions(-) diff --git a/static/js/terrariumpi.js b/static/js/terrariumpi.js index d22ef74b3..8d410073b 100644 --- a/static/js/terrariumpi.js +++ b/static/js/terrariumpi.js @@ -351,18 +351,16 @@ function update_dashboard_power_usage(data) { update_dashboard_tile('power_wattage', formatNumber(data.current) + '/' + formatNumber(data.max)); var percentage = (data.max > 0 ? (data.current / data.max) * 100 : 0); $("#power_wattage .progress-bar-success").css('height', percentage + '%'); - data.total /= 1000; - $("#total_power .count_bottom .costs span").text(formatCurrency(data.price * data.total)); + $("#total_power .count_bottom .costs span").text(formatCurrency(data.price)); $("#total_power .count_bottom span.duration").text(moment.duration(data.duration * 1000).humanize()); - update_dashboard_tile('total_power',formatNumber(data.total)); + update_dashboard_tile('total_power',formatNumber(data.total / (3600 * 1000))); } function update_dashboard_water_flow(data) { update_dashboard_tile('water_flow', formatNumber(data.current) + '/' + formatNumber(data.max)); var percentage = (data.max > 0 ? (data.current / data.max) * 100 : 0); $("#water_flow .progress-bar-info").css('height', percentage + '%'); - data.total /= 1000; - $("#total_water .count_bottom .costs span").text(formatCurrency(data.price * data.total)); + $("#total_water .count_bottom .costs span").text(formatCurrency(data.price)); $("#total_water .count_bottom span.duration").text(moment.duration(data.duration * 1000).humanize()); update_dashboard_tile('total_water', formatNumber(data.total)); } @@ -824,13 +822,7 @@ function load_history_graph(id,type,data_url,nocache) { $.each(online_data, function(dummy, value) { $.each(value, function(dummy, data_array) { globals.graphs[id].timestamp = now; - if (type == 'switch') { - globals.graphs[id].data = process_switch_data(data_array); - } else if (type == 'door') { - globals.graphs[id].data = process_door_data(data_array); - } else { - globals.graphs[id].data = data_array; - } + globals.graphs[id].data = data_array; }); }); @@ -938,6 +930,7 @@ function history_graph(name, data, type) { data: data.alarm_max }]; break; + case 'weather': case 'system_temperature': graph_data = [{ @@ -945,6 +938,7 @@ function history_graph(name, data, type) { data: data }]; break; + case 'system_uptime': delete(graph_options.series.curvedLines); graph_options.series.lines = { @@ -960,6 +954,7 @@ function history_graph(name, data, type) { $('div.row.uptime .x_title small').text(moment.duration(data[data.length-1][1] * 1000).humanize()); break; + case 'system_load': graph_data = [{ label: '{{_('Load')}}', @@ -972,6 +967,7 @@ function history_graph(name, data, type) { data: data.load15 }]; break; + case 'system_memory': graph_data = [{ label: '{{_('Used memory')}}', @@ -984,6 +980,7 @@ function history_graph(name, data, type) { data: data.total }]; break; + case 'switch': delete(graph_options.series.curvedLines); graph_options.series.lines = { @@ -991,16 +988,16 @@ function history_graph(name, data, type) { lineWidth: 2, fill: true }; - - graph_data = [data.power_wattage, data.water_flow]; graph_data = [{ label: '{{_('Power usage in Watt')}}', data: data.power_wattage }, { label: '{{_('Water flow in L/m')}}', - data: data.water_flow + data: data.water_flow, + //yaxis: 2 }]; break; + case 'door': delete(graph_options.series.curvedLines); graph_options.series.lines = { @@ -1008,15 +1005,13 @@ function history_graph(name, data, type) { lineWidth: 2, fill: true }; - - graph_data = [data.state]; graph_data = [{ label: '{{_('Door status')}}', data: data.state }]; break; - } + if (graph_data[0].data != undefined && graph_data[0].data.length > 0) { var total_data_duration = (graph_data[0].data[graph_data[0].data.length - 1][0] - graph_data[0].data[0][0]) / 3600000; graph_options.xaxis.tickSize[0] = Math.round(total_data_duration * 2.5); @@ -1028,18 +1023,18 @@ function history_graph(name, data, type) { if (type == 'switch') { var usage = ''; - if (data.total_power_usage > 0) { - usage = '{{_('Total power in kWh')}}: ' + formatNumber(data.total_power_usage); + if (data.totals.power_wattage.duration > 0) { + usage = '{{_('Duration')}}: ' + moment.duration(data.totals.power_wattage.duration * 1000).humanize() + } + if (data.totals.power_wattage.wattage > 0) { + usage += (usage != '' ? ' - ' : '') + '{{_('Total power in kWh')}}: ' + formatNumber(data.totals.power_wattage.wattage / (3600 * 1000)); } - if (data.total_water_usage > 0) { - usage += (usage != '' ? ' - ' : '') + '{{_('Total water in L')}}: ' + formatNumber(data.total_water_usage); + if (data.totals.water_flow.water > 0) { + usage += (usage != '' ? ' - ' : '') + '{{_('Total water in L')}}: ' + formatNumber(data.totals.water_flow.water); } $('#' + name + ' .total_usage').text(usage); } else if (type == 'door') { - var usage = ''; - if (data.open > 0) { - usage = '{{_('Total open for')}}: ' + moment.duration(data.open).humanize(); - } + var usage = '{{_('Total open for')}}: ' + moment.duration(data.totals.duration).humanize(); $('#' + name + ' .total_usage').text(usage); } $('#' + name + ' .history_graph').bind('plothover', function (event, pos, item) { @@ -1256,119 +1251,6 @@ function toggleSwitch(id) { }); } -function process_graph_data(type, raw_data) { - var graphdata = {} - switch (type) { - case 'door': - graphdata.state = []; - graphdata.open = 0; - break; - case 'switch': - graphdata.power_wattage = []; - graphdata.water_flow = []; - graphdata.total_power_usage = 0; - graphdata.total_water_usage = 0; - break; - } - - var state_change = -1; - $.each(raw_data.state, function(counter, status) { - // Sanitize input - switch (type) { - case 'door': - status[1] = (status[1] === 'closed' ? 0 : 1) - break; - case 'switch': - if (!status[1]) { - raw_data.power_wattage[counter][1] = 0; - raw_data.water_flow[counter][1] = 0; - } - break; - } - - if (state_change != status[1]) { - // Copy previous object to get the right status with current timestamp - var copy = []; - if (counter > 0) { - $.each(graphdata, function(name,data){ - if (typeof graphdata[name] == 'object' ) { - copy = $.extend(true, [], raw_data[name][counter-1]); - // If turned down/off/closed, calculate usage, else it is zero! - var usage = (copy[1] != 0 ? (status[0] - copy[0]) / 1000 * copy[1] : 0); - switch (name) { - case 'state': - graphdata.open += usage; - break; - case 'power_wattage': - graphdata.total_power_usage += usage; - break; - case 'water_flow': - graphdata.total_water_usage += usage; - break; - } - copy[0] = status[0]; - graphdata[name].push(copy); - } - }); - } else if (counter == 0 && status[1] == 1 && type == 'door') { - // If starting with status up/on add a status down/off first for nice graphing - $.each(graphdata, function(name,data){ - if (typeof graphdata[name] == 'object' ) { - copy = $.extend(true, [], raw_data[name][counter]); - // If turned down/off/closed, calculate usage, else it is zero! - var usage = (copy[1] != 0 ? (status[0] - copy[0]) / 1000 * copy[1] : 0); - switch (name) { - case 'state': - graphdata.open += usage; - break; - case 'power_wattage': - graphdata.total_power_usage += usage; - break; - case 'water_flow': - graphdata.total_water_usage += usage; - break; - } - copy[1] = 0; - graphdata[name].push(copy); - } - }); - } - state_change = status[1]; - } - $.each(graphdata, function(name,data){ - if (typeof graphdata[name] == 'object' ) { - graphdata[name].push(raw_data[name][counter]); - } - }); - }); - // Add end data to now... and a startdate of 24 hours ago if needed - var now = new Date().getTime(); - var start = now - (24 * 60 * 60 * 1000); - $.each(graphdata, function(name,data){ - if (typeof graphdata[name] == 'object' ) { - graphdata[name].push([now,graphdata[name][graphdata[name].length-1][1]]); - // Add begin timestamp 24 hours back if needed - if (graphdata[name][0][0] > start) { - graphdata[name].unshift([start,graphdata[name][0][1]]); - } - } - }); - if (type === 'switch') { - graphdata.total_power_usage /= 3600; // To kWh - graphdata.total_water_usage /= 60; // To liters - } - // Return data - return graphdata; -} - -function process_switch_data(raw_data) { - return process_graph_data('switch', raw_data); -} - -function process_door_data(raw_data) { - return process_graph_data('door', raw_data); -} - function update_webcam_preview(name, url) { $('img#webcam_' + name + '_preview').attr('src', url); } @@ -1417,7 +1299,7 @@ function createWebcamLayer(webcamid, maxzoom) { function load_door_history() { $.getJSON('/api/history/doors', function(door_data) { var door_status = {}; - $.each(door_data.door, function(counter, statedata) { + $.each(door_data.doors, function(counter, statedata) { for (var i = 0; i < statedata.state.length; i++) { if (i == 0 || statedata.state[i][1] != statedata.state[i-1][1]) { door_status[statedata.state[i][0]] = statedata.state[i][1]; @@ -1426,7 +1308,7 @@ function load_door_history() { }); // Sort door data events on time. Needed if you have more than one door $.each(Object.keys(door_status).sort(), function(counter,change_time) { - update_door_messages((door_status[change_time] == 'open'), change_time); + update_door_messages((door_status[change_time] === 1), change_time); }) }); } @@ -1463,9 +1345,6 @@ function version_check() { } function init_wysiwyg() { - - if (typeof ($.fn.wysiwyg) === 'undefined') { return; } - function init_ToolbarBootstrapBindings() { var fonts = [ 'Serif', 'Sans', 'Arial', 'Arial Black', 'Courier', diff --git a/terrariumCollector.py b/terrariumCollector.py index ee1754eab..7e37458ec 100644 --- a/terrariumCollector.py +++ b/terrariumCollector.py @@ -96,6 +96,40 @@ def __create_database_structure(self): self.db.commit() + def __recover(self): + # Based on: http://www.dosomethinghere.com/2013/02/20/fixing-the-sqlite-error-the-database-disk-image-is-malformed/ + # Enable recovery status + self.__recovery = True + logger.warn('TerrariumPI Collecter recovery mode is starting! %s', (self.__recovery,)) + + # Create empty sql dump variable + sqldump = '' + lines = 0 + with open('.recovery.sql', 'w') as f: + # Dump SQL data line for line + for line in self.db.iterdump(): + lines += 1 + sqldump += line + "\n" + f.write('%s\n' % line) + + logger.warn('TerrariumPI Collecter recovery mode created SQL dump of %s lines and %s bytes!', (lines,strlen(sqldump),)) + + # Delete broken db + os.remove(terrariumCollector.database) + logger.warn('TerrariumPI Collecter recovery mode deleted faulty database from disk %s', (terrariumCollector.database,)) + + # Reconnect will recreate the db + logger.warn('TerrariumPI Collecter recovery mode starts reconnecting database to create a new clean database at %s', (terrariumCollector.database,)) + self.__connect() + cur = self.db.cursor() + # Load the SQL data back to db + cur.executescript(sqldump) + logger.warn('TerrariumPI Collecter recovery mode restored the old data in a new database. %s', (terrariumCollector.database,)) + + # Return to normal mode + self.__recovery = False + logger.warn('TerrariumPI Collecter recovery mode is finished! %s', (self.__recovery,)) + def __log_data(self,type,id,newdata): if self.__recovery: logger.warn('TerrariumPI Collecter is in recovery mode. Cannot store new logging data!') @@ -114,13 +148,21 @@ def __log_data(self,type,id,newdata): cur.execute('REPLACE INTO sensor_data (id, type, timestamp, current, limit_min, limit_max, alarm_min, alarm_max, alarm) VALUES (?,?,?,?,?,?,?,?,?)', (id, type, now, newdata['current'], newdata['limit_min'], newdata['limit_max'], newdata['alarm_min'], newdata['alarm_max'], newdata['alarm'])) + if type in ['weather']: + cur.execute('REPLACE INTO weather_data (timestamp, wind_speed, temperature, pressure, wind_direction, weather, icon) VALUES (?,?,?,?,?,?,?)', + (now, newdata['wind_speed'], newdata['temperature'], newdata['pressure'], newdata['wind_direction'], newdata['weather'], newdata['icon'])) + + if type in ['system']: + cur.execute('REPLACE INTO system_data (timestamp, load_load1, load_load5, load_load15, uptime, temperature, cores, memory_total, memory_used, memory_free) VALUES (?,?,?,?,?,?,?,?,?,?)', + (now, newdata['load']['load1'], newdata['load']['load5'], newdata['load']['load15'], newdata['uptime'], newdata['temperature'], newdata['cores'], newdata['memory']['total'], newdata['memory']['used'], newdata['memory']['free'])) + if type in ['switches']: if 'time' in newdata: now = newdata['time'] # Make a duplicate of last state and save it with 1 sec back in time to smooth the graphs cur.execute('''REPLACE INTO switch_data (id,timestamp,state,power_wattage,water_flow) - SELECT id, ? as timestamp,state,power_wattage,water_flow + SELECT id, ? as curtimestamp,state,power_wattage,water_flow FROM switch_data WHERE id = ? ORDER BY timestamp DESC LIMIT 1''', (now-1, id)) @@ -128,72 +170,107 @@ def __log_data(self,type,id,newdata): (id, now, newdata['state'], newdata['power_wattage'], newdata['water_flow'])) if type in ['door']: + # Make a duplicate of last state and save it with 1 sec back in time to smooth the graphs + cur.execute('''REPLACE INTO door_data (id,timestamp,state) + SELECT id, ? as curtimestamp,state + FROM door_data + WHERE id = ? ORDER BY timestamp DESC LIMIT 1''', (now-1, id)) + cur.execute('REPLACE INTO door_data (id, timestamp, state) VALUES (?,?,?)', (id, now, newdata)) - if type in ['weather']: - cur.execute('REPLACE INTO weather_data (timestamp, wind_speed, temperature, pressure, wind_direction, weather, icon) VALUES (?,?,?,?,?,?,?)', - (now, newdata['wind_speed'], newdata['temperature'], newdata['pressure'], newdata['wind_direction'], newdata['weather'], newdata['icon'])) - - if type in ['system']: - cur.execute('REPLACE INTO system_data (timestamp, load_load1, load_load5, load_load15, uptime, temperature, cores, memory_total, memory_used, memory_free) VALUES (?,?,?,?,?,?,?,?,?,?)', - (now, newdata['load']['load1'], newdata['load']['load5'], newdata['load']['load15'], newdata['uptime'], newdata['temperature'], newdata['cores'], newdata['memory']['total'], newdata['memory']['used'], newdata['memory']['free'])) - self.db.commit() except sqlite3.DatabaseError as ex: logger.error('TerrariumPI Collecter exception! %s', (ex,)) if 'database disk image is malformed' == str(ex): self.__recover() - def __recover(self): - # Based on: http://www.dosomethinghere.com/2013/02/20/fixing-the-sqlite-error-the-database-disk-image-is-malformed/ - # Enable recovery status - self.__recovery = True - logger.warn('TTerrariumPI Collecter recovery mode is starting! %s', (self.__recovery,)) + def __calculate_power_and_water_usage(self,history): + if 'switches' not in history: + return - # Create empty sql dump variable - sqldump = '' - lines = 0 - with open('.recovery.sql', 'w') as f: - # Dump SQL data line for line - for line in self.db.iterdump(): - lines += 1 - sqldump += line + "\n" - f.write('%s\n' % line) + now = int(time.time()) * 1000 + for switchid in history['switches']: + # First add a new element to all the data arrays with the current timestamp. This is needed for: + # - Better power usage calculation + # - Better graphs in the interface + history['switches'][switchid]['power_wattage'].append([now,history['switches'][switchid]['power_wattage'][-1][1]]) + history['switches'][switchid]['water_flow'].append([now,history['switches'][switchid]['water_flow'][-1][1]]) + history['switches'][switchid]['state'].append([now,history['switches'][switchid]['state'][-1][1]]) + + totals = {'power_wattage' : {'duration' : 0.0 , 'wattage' : 0.0, 'price' : 0.0}, + 'water_flow' : {'duration' : 0.0 , 'water' : 0.0, 'price' : 0.0}} + power_on_time = None + for counter,state in enumerate(history['switches'][switchid]['state']): + if state[1] > 0 and power_on_time is None: # Power went on! The value could be variable from zero to 100. Above zero is 'on' + power_on_time = counter + elif power_on_time is not None: # ..... + power_wattage_start = history['switches'][switchid]['power_wattage'][power_on_time][1] * (history['switches'][switchid]['state'][power_on_time][1] / 100.0) + power_wattage_end = history['switches'][switchid]['power_wattage'][counter][1] * (state[1] / 100.0) + power_wattage = (power_wattage_start + power_wattage_end) / 2.0 + + water_flow_start = history['switches'][switchid]['water_flow'][power_on_time][1] * (history['switches'][switchid]['state'][power_on_time][1] / 100.0) + water_flow_end = history['switches'][switchid]['water_flow'][counter][1] * (state[1] / 100.0) + water_flow = (water_flow_start + water_flow_end) / 2.0 + + duration = (state[0] - history['switches'][switchid]['state'][power_on_time][0]) / 1000.0 # Devide by 1000 because history is using Javascript timestamps + + totals['power_wattage']['duration'] += duration + totals['power_wattage']['wattage'] += (duration * power_wattage) + + totals['water_flow']['duration'] += duration + totals['water_flow']['water'] += (duration * (water_flow / 60)) # Water flow is in Liter per minute. So devide by 60 to get per seconds + + if state[1] == 0: + power_on_time = None # Power went down. Reset so we can measure new period + else: + power_on_time = counter # Change in power useage (dimmer) - logger.warn('TerrariumPI Collecter recovery mode created SQL dump of %s lines and %s bytes!', (lines,strlen(sqldump),)) + # Here we change the wattage and water flow to zero if the switch was off. This is needed for drawing the right graphs + if state[1] == 0: + history['switches'][switchid]['power_wattage'][counter][1] = 0 + history['switches'][switchid]['water_flow'][counter][1] = 0 + else: + history['switches'][switchid]['power_wattage'][counter][1] *= (state[1] / 100.0) + history['switches'][switchid]['water_flow'][counter][1] *= (state[1] / 100.0) - # Delete broken db - os.remove(terrariumCollector.database) - logger.warn('TerrariumPI Collecter recovery mode deleted faulty database from disk %s', (terrariumCollector.database,)) + history['switches'][switchid]['totals'] = totals - # Reconnect will recreate the db - logger.warn('TerrariumPI Collecter recovery mode starts reconnecting database to create a new clean database at %s', (terrariumCollector.database,)) - self.__connect() - cur = self.db.cursor() - # Load the SQL data back to db - cur.executescript(sqldump) - logger.warn('TerrariumPI Collecter recovery mode restored the old data in a new database. %s', (terrariumCollector.database,)) + def __calculate_door_usage(self,history): + if 'doors' not in history: + return - # Return to normal mode - self.__recovery = False - logger.warn('TerrariumPI Collecter recovery mode is finished! %s', (self.__recovery,)) + now = int(time.time()) * 1000 + for doorid in history['doors']: + history['doors'][doorid]['state'].append([now,history['doors'][doorid]['state'][-1][1]]) + + totals = {'duration': 0} + door_open_on_time = None + for counter,state in enumerate(history['doors'][doorid]['state']): + if state[1] != 'closed' and door_open_on_time is None: # Door went open! + door_open_on_time = counter + elif state[1] == 'closed' and door_open_on_time is not None: # Door is closed. Calc period and data + totals['duration'] += (state[0] - history['doors'][doorid]['state'][door_open_on_time][0]) / 1000.0 # Devide by 1000 because history is using Javascript timestamps + door_open_on_time = None # Reset so we can measure new period + + # Here we translate closed to zero and open to one. Else the graphs will not work + history['doors'][doorid]['state'][counter][1] = (0 if state[1] == 'closed' else 1) + + history['doors'][doorid]['totals'] = totals def log_switch_data(self,switch): switch_id = switch['id'] del(switch['id']) - del(switch['hardwaretype']) del(switch['address']) del(switch['name']) - - # Rather updating a big database, we translate some fields here - switch['power_wattage'] = switch['current_power_wattage'] - switch['water_flow'] = switch['current_water_flow'] del(switch['current_power_wattage']) del(switch['current_water_flow']) - if 'init' in switch: - del(switch['init']) + if switch['hardwaretype'] != 'pwm-dimmer': + # Store normal switches with value 100 indicating full power (aka no dimming) + switch['state'] = (100 if switch['state'] == 1 else 0) + + del(switch['hardwaretype']) self.__log_data('switches',switch_id,switch) def log_door_data(self,door): @@ -216,84 +293,6 @@ def log_sensor_data(self,sensor): def log_system_data(self, data): self.__log_data('system',None,data) - def log_total_power_and_water_usage(self,pi_wattage): - if self.__recovery: - logger.warn('TerrariumPI Collecter is in recovery mode. Cannot store new power and water total usage!') - return - - today = int(time.time()) - time_past = today % 86400 - today -= time_past - data = {'day' : today, 'on': 0, 'power' : time_past * pi_wattage, 'water' : 0} - - sql = '''SELECT - switch_data_duration.id, - switch_data_duration.timestamp AS aan, - switch_data.timestamp AS uit, - switch_data.power_wattage, - switch_data.water_flow , - switch_data.timestamp - max(switch_data_duration.timestamp) AS duration - FROM switch_data left JOIN switch_data as switch_data_duration - ON switch_data.id = switch_data_duration.id - AND switch_data.timestamp > switch_data_duration.timestamp - AND switch_data_duration.id <> 'total' - WHERE switch_data_duration.id NOT null - AND switch_data.id <> 'total' - AND switch_data.timestamp >= ? - AND switch_data_duration.timestamp < ? - GROUP BY switch_data.id, switch_data.timestamp - HAVING switch_data_duration.state = 1 - AND switch_data.timestamp >= ? - AND switch_data_duration.timestamp < ? - ORDER BY aan ASC''' - - filters = (today,today+86400,today,today+86400,) - rows = [] - try: - with self.db: - cur = self.db.cursor() - cur.execute(sql, filters) - rows = cur.fetchall() - except sqlite3.DatabaseError as ex: - logger.error('TerrariumPI Collecter exception! %s', (ex,)) - if 'database disk image is malformed' == str(ex): - self.__recover() - - for row_tmp in rows: - row = {'aan': row_tmp['aan'], - 'uit':row_tmp['uit'], - 'duration' : row_tmp['duration'], - 'power_wattage' : row_tmp['power_wattage'], - 'water_flow' : row_tmp['water_flow']} - - if row['aan'] < today: - row['duration'] -= today - row['aan'] - row['aan'] = today - - if row['uit'] > today+86400: - row['duration'] -= row['uit'] - (today+86400) - row['uit'] = today+86400 - - data['on'] += row['duration'] - data['power'] += row['duration'] * row['power_wattage'] - data['water'] += row['duration'] * row['water_flow'] - - # Power is in Wh (Watt/hour) so devide by 3600 seconds - data['power'] /= 3600 - # Water is in Liters - data['water'] /= 60 - - try: - with self.db: - cur = self.db.cursor() - cur.execute('REPLACE INTO switch_data (id, timestamp, state, power_wattage, water_flow) VALUES (?,?,?,?,?)', - ('total', today, data['on'], data['power'], data['water'])) - self.db.commit() - except sqlite3.DatabaseError as ex: - logger.error('TerrariumPI Collecter exception! %s', (ex,)) - if 'database disk image is malformed' == str(ex): - self.__recover() - def get_history(self, parameters = [], starttime = None, stoptime = None): # Default return object history = {} @@ -351,25 +350,13 @@ def get_history(self, parameters = [], starttime = None, stoptime = None): elif logtype == 'switches': fields = { 'power_wattage' : [], 'water_flow' : [] , 'state' : []} sql = 'SELECT id, "switches" as type, timestamp, ' + ', '.join(fields.keys()) + ' FROM switch_data WHERE timestamp >= ? and timestamp <= ? ' - if len(parameters) > 0 and parameters[0] == 'summary': - fields = ['total_power', 'total_water', 'duration'] - filters = ('total',) - # Temporary overrule.... :P - sql = ''' - SELECT ''' + str(stoptime) + ''' as timestamp, - IFNULL(MAX(timestamp) - MIN(timestamp),0) as duration, - IFNULL(SUM(power_wattage),0) as total_power, - IFNULL(SUM(water_flow),0) as total_water - FROM switch_data - WHERE id = ? ''' - - elif len(parameters) > 0 and parameters[0] is not None: + if len(parameters) > 0 and parameters[0] is not None: sql = sql + ' and id = ?' filters = (stoptime,starttime,parameters[0],) elif logtype == 'doors': fields = { 'state' : []} - sql = 'SELECT id, "door" as type, timestamp, ' + ', '.join(fields.keys()) + ' FROM door_data WHERE timestamp >= ? and timestamp <= ? ' + sql = 'SELECT id, "doors" as type, timestamp, ' + ', '.join(fields.keys()) + ' FROM door_data WHERE timestamp >= ? and timestamp <= ? ' if len(parameters) > 0 and parameters[0] is not None: sql = sql + ' and id = ?' @@ -441,4 +428,9 @@ def get_history(self, parameters = [], starttime = None, stoptime = None): for field in fields: history[row['type']][row['id']][field].append([row['timestamp'] * 1000,row[field]]) + if logtype == 'switches': + self.__calculate_power_and_water_usage(history) + elif logtype == 'doors': + self.__calculate_door_usage(history) + return history diff --git a/terrariumEngine.py b/terrariumEngine.py index 1ec6fbea7..09bbf90c2 100644 --- a/terrariumEngine.py +++ b/terrariumEngine.py @@ -166,6 +166,9 @@ def __load_doors(self, reloading = False): ) self.doors[door.get_id()] = door + if not reloading: + self.toggle_door_status(door.get_data()) + logger.info('Done %s terrariumPI doors. Found %d doors in %.3f seconds' % ('reloading' if reloading else 'loading', len(self.doors), time.time()-starttime)) @@ -189,7 +192,7 @@ def __load_webcams(self, reloading = False): len(self.webcams), time.time()-starttime)) - def __get_power_usage_water_flow(self, socket = False): + def __get_current_power_usage_water_flow(self, socket = False): data = {'power' : {'current' : self.pi_power_wattage , 'max' : self.pi_power_wattage}, 'water' : {'current' : 0.0 , 'max' : 0.0}} @@ -202,8 +205,20 @@ def __get_power_usage_water_flow(self, socket = False): return data - def __calculate_power_usage_water_flow(self): - return self.collector.get_history(['switches','summary']) + def __get_total_power_usage_water_flow(self): + totals = {'power_wattage' : {'duration' : 0.0 , 'wattage' : 0.0, 'price' : 0.0}, + 'water_flow' : {'duration' : 0.0 , 'water' : 0.0, 'price' : 0.0}} + + history = self.collector.get_history(['switches'],int(time.time()),0) + + for switchid in history['switches']: + totals['power_wattage']['duration'] += history['switches'][switchid]['totals']['power_wattage']['duration'] + totals['power_wattage']['wattage'] += history['switches'][switchid]['totals']['power_wattage']['wattage'] + + totals['water_flow']['duration'] += history['switches'][switchid]['totals']['water_flow']['duration'] + totals['water_flow']['water'] += history['switches'][switchid]['totals']['water_flow']['water'] + + return totals def __engine_loop(self): while True: @@ -230,18 +245,6 @@ def __engine_loop(self): # Websocket callback self.__send_message({'type':'sensor_gauge','data':average_data}) - # Calculate power and water usage per day every 9th minute - # Disabled again! - ''' - if int(time.strftime('%M')) % 10 == 9: - for powerswitchid in self.power_switches: - self.collector.log_switch_data(self.power_switches[powerswitchid].get_data()) - self.collector.log_total_power_and_water_usage(self.pi_power_wattage) - - for doorid in self.doors: - self.collector.log_door_data(self.doors[doorid].get_data()) - ''' - # Websocket messages back self.get_uptime(socket=True) self.get_power_usage_water_flow(socket=True) @@ -436,8 +439,6 @@ def toggle_switch(self,data): if self.environment is not None: self.get_environment(socket=True) - if data['state'] is False: - self.collector.log_total_power_and_water_usage(self.pi_power_wattage) # End switches part @@ -700,15 +701,16 @@ def get_uptime(self, socket = False): return data def get_power_usage_water_flow(self, socket = False): - data = self.__get_power_usage_water_flow() - totaldata = self.__calculate_power_usage_water_flow() - - data['power']['total'] = totaldata['total_power'] - data['power']['duration'] = totaldata['duration'] - data['power']['price'] = self.config.get_power_price() - data['water']['total'] = totaldata['total_water'] - data['water']['duration'] = totaldata['duration'] - data['water']['price'] = self.config.get_water_price() + data = self.__get_current_power_usage_water_flow() + totaldata = self.__get_total_power_usage_water_flow() + + data['power']['total'] = totaldata['power_wattage']['wattage'] + data['power']['duration'] = totaldata['power_wattage']['duration'] + data['power']['price'] = self.config.get_power_price() * (totaldata['power_wattage']['wattage'] / (3600 * 1000)) + + data['water']['total'] = totaldata['water_flow']['water'] + data['water']['duration'] = totaldata['water_flow']['duration'] + data['water']['price'] = self.config.get_water_price() * totaldata['water_flow']['water'] if socket: self.__send_message({'type':'power_usage_water_flow','data':data}); From 5987065b33cbf5eba84d19fcba80f28e17cfc97e Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sun, 22 Oct 2017 20:06:40 +0200 Subject: [PATCH 16/34] Updated installation so it works faster and handles upgrades better --- install.sh | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/install.sh b/install.sh index 41a999a32..00a3e8e1f 100755 --- a/install.sh +++ b/install.sh @@ -15,14 +15,7 @@ apt-get -y autoremove # Install required packages to get the terrarium software running aptitude -y update aptitude -y safe-upgrade -aptitude -y install libftdi1 screen python-imaging python-dateutil python-ow python-rpi.gpio python-psutil git subversion watchdog build-essential python-dev python-picamera python-opencv python-pip python-pigpio - -if [ `ls -l gentelella | grep -v ^t | wc -l` -eq 0 ]; then - # Manual get Gentelella bootstrap 3 template - git clone https://github.com/puikinsh/gentelella.git gentelella -fi - -cd .. +aptitude -y install libftdi1 screen python-imaging python-dateutil python-ow python-rpi.gpio python-psutil git subversion watchdog build-essential python-dev python-picamera python-opencv python-pip python-pigpio i2c-tools owfs ow-shell # Basic config: raspi-config @@ -30,23 +23,36 @@ raspi-config # Set the timezone dpkg-reconfigure tzdata +if [ `ls -l gentelella | grep -v ^t | wc -l` -eq 0 ]; then + # Manual get Gentelella bootstrap 3 template + git clone https://github.com/puikinsh/gentelella.git gentelella +fi + +cd gentelella +git pull +cd "${BASEDIR}/.." + # Install multiple python modules -pip install --upgrade gevent untangle uptime bottle bottle_websocket +pip install --upgrade gevent untangle uptime bottle bottle_websocket pylibftdi # Install https://pypi.python.org/pypi/pylibftdi # Docu https://pylibftdi.readthedocs.io/ -pip install --upgrade pylibftdi # Make sure that the normal Pi user can read and write to the usb driver echo 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", GROUP="dialout", MODE="0660"' > /etc/udev/rules.d/99-libftdi.rules echo 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", GROUP="dialout", MODE="0660"' >> /etc/udev/rules.d/99-libftdi.rules # Install 1 Wire stuff -aptitude -y install i2c-tools owfs ow-shell sed -i.bak 's/^server: FAKE = DS18S20,DS2405/#server: FAKE = DS18S20,DS2405/' /etc/owfs.conf -echo "server: device=/dev/i2c-1" >> /etc/owfs.conf + +if [ `grep -ic "server: device=/dev/i2c-1" /etc/owfs.conf` -eq 0 ]; then + echo "server: device=/dev/i2c-1" >> /etc/owfs.conf +fi sed -i.bak 's/^blacklist i2c-bcm2708/#blacklist i2c-bcm2708/' /etc/modprobe.d/raspi-blacklist.conf -echo "i2c-dev" >> /etc/modules +if [ `grep -ic "i2c-dev" /etc/modules` -eq 0 ]; then + echo "i2c-dev" >> /etc/modules +fi + modprobe i2c-bcm2708 modprobe i2c-dev @@ -58,6 +64,7 @@ fi cd Adafruit_Python_DHT git pull sudo python setup.py install +cd "${BASEDIR}/.." # Remove unneeded OWS services update-rc.d -f owftpd remove @@ -75,7 +82,7 @@ usermod -a -G gpio pi 2> /dev/null echo "pi ALL=(ALL) NOPASSWD: /usr/sbin/service pigpiod restart" > /etc/sudoers.d/terrariumpi systemctl enable pigpiod -sync - # We are done! +sync echo "Instaltion is done. Please reboot once to get the I2C and Adafruit DHT libary working correctly" + From a8293f3a49834c75728b458b5ded142dd598668c Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sun, 22 Oct 2017 20:50:30 +0200 Subject: [PATCH 17/34] Fix switch loading without dimmer settings --- terrariumEngine.py | 64 ++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/terrariumEngine.py b/terrariumEngine.py index 09bbf90c2..5443673da 100644 --- a/terrariumEngine.py +++ b/terrariumEngine.py @@ -112,36 +112,34 @@ def __load_power_switches(self,reloading = False): seen_switches = [] for power_switch_config in switch_config: - seen_switches.append(switch_config[power_switch_config]['id']) - if switch_config[power_switch_config]['id'] in self.power_switches: - # Update switch - self.power_switches[switch_config[power_switch_config]['id']].set_name(switch_config[power_switch_config]['name']) - self.power_switches[switch_config[power_switch_config]['id']].set_power_wattage(switch_config[power_switch_config]['power_wattage']) - self.power_switches[switch_config[power_switch_config]['id']].set_water_flow(switch_config[power_switch_config]['water_flow']) - self.power_switches[switch_config[power_switch_config]['id']].set_dimmer_on_duration(switch_config[power_switch_config]['dimmer_on_duration']) - self.power_switches[switch_config[power_switch_config]['id']].set_dimmer_on_percentage(switch_config[power_switch_config]['dimmer_on_percentage']) - self.power_switches[switch_config[power_switch_config]['id']].set_dimmer_off_duration(switch_config[power_switch_config]['dimmer_off_duration']) - self.power_switches[switch_config[power_switch_config]['id']].set_dimmer_off_percentage(switch_config[power_switch_config]['dimmer_off_percentage']) - - else: - power_switch = terrariumSwitch( - switch_config[power_switch_config]['id'], - switch_config[power_switch_config]['hardwaretype'], - switch_config[power_switch_config]['address'], - switch_config[power_switch_config]['name'], - switch_config[power_switch_config]['power_wattage'], - switch_config[power_switch_config]['water_flow'], - switch_config[power_switch_config]['dimmer_on_duration'], - switch_config[power_switch_config]['dimmer_on_percentage'], - switch_config[power_switch_config]['dimmer_off_duration'], - switch_config[power_switch_config]['dimmer_off_percentage'], - self.toggle_switch - ) - self.power_switches[power_switch.get_id()] = power_switch + power_switch_id = switch_config[power_switch_config]['id'] + seen_switches.append(power_switch_id) + if not power_switch_id in self.power_switches: + # Add new switch + power_switch = terrariumSwitch(switch_config[power_switch_config]['id'], + switch_config[power_switch_config]['hardwaretype'], + switch_config[power_switch_config]['address'], + callback=self.toggle_switch) + power_switch_id = power_switch.get_id() + self.power_switches[power_switch_id] = power_switch + + # Update switch + self.power_switches[power_switch_id].set_name(switch_config[power_switch_config]['name']) + self.power_switches[power_switch_id].set_power_wattage(switch_config[power_switch_config]['power_wattage']) + self.power_switches[power_switch_id].set_water_flow(switch_config[power_switch_config]['water_flow']) + + if 'dimmer_on_duration' in switch_config[power_switch_config]: + self.power_switches[power_switch_id].set_dimmer_on_duration(switch_config[power_switch_config]['dimmer_on_duration']) + if 'dimmer_on_percentage' in switch_config[power_switch_config]: + self.power_switches[power_switch_id].set_dimmer_on_percentage(switch_config[power_switch_config]['dimmer_on_percentage']) + if 'dimmer_off_duration' in switch_config[power_switch_config]: + self.power_switches[power_switch_id].set_dimmer_off_duration(switch_config[power_switch_config]['dimmer_off_duration']) + if 'dimmer_off_percentage' in switch_config[power_switch_config]: + self.power_switches[power_switch_id].set_dimmer_off_percentage(switch_config[power_switch_config]['dimmer_off_percentage']) for power_switch_id in set(self.power_switches) - set(seen_switches): # clean up old deleted switches - del(self.power_switches[switch_config[power_switch_config]['id']]) + del(self.power_switches[power_switch_id]) if reloading: self.environment.set_power_switches(self.power_switches) @@ -417,10 +415,14 @@ def set_switches_config(self, data): power_switch.set_name(switchdata['name']) power_switch.set_power_wattage(switchdata['power_wattage']) power_switch.set_water_flow(switchdata['water_flow']) - power_switch.set_dimmer_on_duration(switchdata['dimmer_on_duration']) - power_switch.set_dimmer_on_percentage(switchdata['dimmer_on_percentage']) - power_switch.set_dimmer_off_duration(switchdata['dimmer_off_duration']) - power_switch.set_dimmer_off_percentage(switchdata['dimmer_off_percentage']) + if 'dimmer_on_duration' in switchdata: + power_switch.set_dimmer_on_duration(switchdata['dimmer_on_duration']) + if 'dimmer_on_percentage' in switchdata: + power_switch.set_dimmer_on_percentage(switchdata['dimmer_on_percentage']) + if 'dimmer_off_duration' in switchdata: + power_switch.set_dimmer_off_duration(switchdata['dimmer_off_duration']) + if 'dimmer_off_percentage' in switchdata: + power_switch.set_dimmer_off_percentage(switchdata['dimmer_off_percentage']) new_switches[power_switch.get_id()] = power_switch From 377dfad5a63ccd909b50e423096146f93ad7fc1f Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sun, 22 Oct 2017 21:44:59 +0200 Subject: [PATCH 18/34] Add PI power user to total power usage --- terrariumEngine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terrariumEngine.py b/terrariumEngine.py index 5443673da..4d339aac3 100644 --- a/terrariumEngine.py +++ b/terrariumEngine.py @@ -204,7 +204,7 @@ def __get_current_power_usage_water_flow(self, socket = False): return data def __get_total_power_usage_water_flow(self): - totals = {'power_wattage' : {'duration' : 0.0 , 'wattage' : 0.0, 'price' : 0.0}, + totals = {'power_wattage' : {'duration' : 0.0 , 'wattage' : self.get_uptime()['uptime'] * self.pi_power_wattage, 'price' : 0.0}, 'water_flow' : {'duration' : 0.0 , 'water' : 0.0, 'price' : 0.0}} history = self.collector.get_history(['switches'],int(time.time()),0) From 1f9fcc7bf5ced76304a75222c63ac6d1b9768d86 Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sun, 22 Oct 2017 22:04:39 +0200 Subject: [PATCH 19/34] Fixed total duration calculation in total power usage --- terrariumEngine.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/terrariumEngine.py b/terrariumEngine.py index 4d339aac3..929cffad4 100644 --- a/terrariumEngine.py +++ b/terrariumEngine.py @@ -204,18 +204,27 @@ def __get_current_power_usage_water_flow(self, socket = False): return data def __get_total_power_usage_water_flow(self): - totals = {'power_wattage' : {'duration' : 0.0 , 'wattage' : self.get_uptime()['uptime'] * self.pi_power_wattage, 'price' : 0.0}, - 'water_flow' : {'duration' : 0.0 , 'water' : 0.0, 'price' : 0.0}} + totals = {'power_wattage' : {'duration' : int(time.time()) , 'wattage' : 0.0, 'price' : 0.0}, + 'water_flow' : {'duration' : int(time.time()) , 'water' : 0.0, 'price' : 0.0}} history = self.collector.get_history(['switches'],int(time.time()),0) for switchid in history['switches']: - totals['power_wattage']['duration'] += history['switches'][switchid]['totals']['power_wattage']['duration'] + if history['switches'][switchid]['power_wattage'][0][0] / 1000.0 < totals['power_wattage']['duration']: + totals['power_wattage']['duration'] = history['switches'][switchid]['power_wattage'][0][0] / 1000.0 + totals['power_wattage']['wattage'] += history['switches'][switchid]['totals']['power_wattage']['wattage'] - totals['water_flow']['duration'] += history['switches'][switchid]['totals']['water_flow']['duration'] + if history['switches'][switchid]['water_flow'][0][0] / 1000.0 < totals['water_flow']['duration']: + totals['water_flow']['duration'] = history['switches'][switchid]['water_flow'][0][0] / 1000.0 + totals['water_flow']['water'] += history['switches'][switchid]['totals']['water_flow']['water'] + totals['power_wattage']['duration'] = int(time.time()) - totals['power_wattage']['duration'] + totals['water_flow']['duration'] = int(time.time()) - totals['water_flow']['duration'] + + totals['power_wattage']['wattage'] += max(totals['power_wattage']['duration'],totals['water_flow']['duration']) * self.pi_power_wattage + return totals def __engine_loop(self): From 00e8c8410e65dd2d35ea996ef4aa68254a7304f5 Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Sun, 22 Oct 2017 23:21:46 +0200 Subject: [PATCH 20/34] Update power duration calculation --- terrariumEngine.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/terrariumEngine.py b/terrariumEngine.py index 929cffad4..4179fa90f 100644 --- a/terrariumEngine.py +++ b/terrariumEngine.py @@ -210,20 +210,19 @@ def __get_total_power_usage_water_flow(self): history = self.collector.get_history(['switches'],int(time.time()),0) for switchid in history['switches']: + totals['power_wattage']['wattage'] += history['switches'][switchid]['totals']['power_wattage']['wattage'] + totals['water_flow']['water'] += history['switches'][switchid]['totals']['water_flow']['water'] + if history['switches'][switchid]['power_wattage'][0][0] / 1000.0 < totals['power_wattage']['duration']: totals['power_wattage']['duration'] = history['switches'][switchid]['power_wattage'][0][0] / 1000.0 - totals['power_wattage']['wattage'] += history['switches'][switchid]['totals']['power_wattage']['wattage'] - if history['switches'][switchid]['water_flow'][0][0] / 1000.0 < totals['water_flow']['duration']: totals['water_flow']['duration'] = history['switches'][switchid]['water_flow'][0][0] / 1000.0 - totals['water_flow']['water'] += history['switches'][switchid]['totals']['water_flow']['water'] - - totals['power_wattage']['duration'] = int(time.time()) - totals['power_wattage']['duration'] - totals['water_flow']['duration'] = int(time.time()) - totals['water_flow']['duration'] + totals['power_wattage']['duration'] = max(self.get_uptime()['uptime'],int(time.time()) - totals['power_wattage']['duration'],int(time.time()) - totals['water_flow']['duration']) + totals['water_flow']['duration'] = totals['power_wattage']['duration'] - totals['power_wattage']['wattage'] += max(totals['power_wattage']['duration'],totals['water_flow']['duration']) * self.pi_power_wattage + totals['power_wattage']['wattage'] += totals['power_wattage']['duration'] * self.pi_power_wattage return totals From 512e087c3f531b8763eba010c3bf33a03dff63a6 Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Tue, 24 Oct 2017 22:01:38 +0200 Subject: [PATCH 21/34] Fix graphing empty graphs and smaller dimmer knob --- static/js/terrariumpi.js | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/static/js/terrariumpi.js b/static/js/terrariumpi.js index 8d410073b..6fdaaf405 100644 --- a/static/js/terrariumpi.js +++ b/static/js/terrariumpi.js @@ -917,8 +917,8 @@ function history_graph(name, data, type) { }; switch (type) { - case 'temperature': case 'humidity': + case 'temperature': graph_data = [{ label: '{{_('Current')}}', data: data.current @@ -982,6 +982,9 @@ function history_graph(name, data, type) { break; case 'switch': + if (data.water_flow === undefined || data.water_flow === undefined || data.water_flow.length == 0 && data.power_wattage.length == 0) { + graph_options.yaxis.min = 0; + } delete(graph_options.series.curvedLines); graph_options.series.lines = { show: true, @@ -994,11 +997,13 @@ function history_graph(name, data, type) { }, { label: '{{_('Water flow in L/m')}}', data: data.water_flow, - //yaxis: 2 }]; break; case 'door': + if (data.state === undefined ||data.state.length == 0) { + graph_options.yaxis.min = 0; + } delete(graph_options.series.curvedLines); graph_options.series.lines = { show: true, @@ -1023,18 +1028,23 @@ function history_graph(name, data, type) { if (type == 'switch') { var usage = ''; - if (data.totals.power_wattage.duration > 0) { - usage = '{{_('Duration')}}: ' + moment.duration(data.totals.power_wattage.duration * 1000).humanize() - } - if (data.totals.power_wattage.wattage > 0) { - usage += (usage != '' ? ' - ' : '') + '{{_('Total power in kWh')}}: ' + formatNumber(data.totals.power_wattage.wattage / (3600 * 1000)); - } - if (data.totals.water_flow.water > 0) { - usage += (usage != '' ? ' - ' : '') + '{{_('Total water in L')}}: ' + formatNumber(data.totals.water_flow.water); + if (data.totals !== undefined) { + if (data.totals.power_wattage.duration > 0) { + usage = '{{_('Duration')}}: ' + moment.duration(data.totals.power_wattage.duration * 1000).humanize() + } + if (data.totals.power_wattage.wattage > 0) { + usage += (usage != '' ? ' - ' : '') + '{{_('Total power in kWh')}}: ' + formatNumber(data.totals.power_wattage.wattage / (3600 * 1000)); + } + if (data.totals.water_flow.water > 0) { + usage += (usage != '' ? ' - ' : '') + '{{_('Total water in L')}}: ' + formatNumber(data.totals.water_flow.water); + } } $('#' + name + ' .total_usage').text(usage); } else if (type == 'door') { - var usage = '{{_('Total open for')}}: ' + moment.duration(data.totals.duration).humanize(); + var usage = ''; + if (data.totals !== undefined) { + usage = '{{_('Total open for')}}: ' + moment.duration(data.totals.duration).humanize(); + } $('#' + name + ' .total_usage').text(usage); } $('#' + name + ' .history_graph').bind('plothover', function (event, pos, item) { @@ -1229,7 +1239,7 @@ function update_power_switch(id, data) { power_switch.find('h2 small.data_update').text(update_data); if (data.hardwaretype === 'pwm-dimmer') { - power_switch.find('div.power_switch').removeClass('big').addClass('dimmer').html(''); + power_switch.find('div.power_switch').removeClass('big').addClass('dimmer').html(''); power_switch.find('.knob').knob({ release: function(value) { From 1a8a8584af5e929aad01d0b2f763c0a374f93dec Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Tue, 24 Oct 2017 22:29:24 +0200 Subject: [PATCH 22/34] Update readme --- README.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6307e90cc..9595f4369 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,33 @@ # TerrariumPI 2.8.2 -Software for cheap home automation of your reptile terrarium or any other enclosed environment. With this software you are able to control an enclosed environment so that the temperature and humidity is of a constant value. This is done by using temperature and humidity sensors and realy switches to activate external devices. +Software for cheap home automation of your reptile terrarium or any other enclosed environment. With this software you are able to control for example a terrarium so that the temperature and humidity is of a constant value. Controlling the temperature can be done with heat lights, external heating or cooling system. As long as there is one temperature sensor available the software is able to keep a constant temperature. -It has support for lights, sprayer, heater and cooler equipment. The amount of devices that can be controlled depends on the used relay boards. +For humidity control there is support for a spraying system. The sprayer can be configured to spray for an X amount of seconds and there is a minumal period between two spray actions. Use at least one humitidy sensors to get a constant humidity value. + +The software is that flexible that there is no limit in amount of sensors, relay boards or door sensors. The usage can be endless. Think off: -- Terrarium +- Terrarium (wet of dry) - Aquarium +- Tanks with animals or plants - Growhouse And all this is controlled with a nice webinterface based on [Gentelella a Bootstrap 3 template](https://github.com/puikinsh/gentelella/). ## Features - Controlling electronic devices like lights, sprayers, heating and cooling equipment - Support for dimming electronic devices -- Reading out temperature and humidity sensors -- Open door detection (sprayer will not spray when a door is open) + - Manual dimming through web interface + - Predefined on and off durations + - Predefined on and off dimming percentages +- Reading out multiple temperature and humidity sensors - Support for native Raspberry Pi cam out of the box - Support for USB and remote webcams +- Open door detection (sprayer will not spray when a door is open) - Total power and water usage for costs calculation - Lights control based on sun rise and sun set or timers -- Rain control based on measured humidity +- Rain control based on humidity sensors and timers - Heater control based on temperature sensors or timers - Cooling control based on temperature sensors or timers -- Weather forecast from external source +- Weather forecast from external source for lighting schema (turing on when sun rises, turning off when sun sets) - Temperatures in Celsius or Fahrenheit - Alarm detections @@ -62,12 +68,14 @@ This updating is based on that the software is installed with the steps in the I `cd TerrariumPI` 3. Update the new code with git `git pull` +4. Re-run the installation script in order to update software dependencies + `sudo ./install.sh` 4. Restart TerrariumPI according to: https://github.com/theyosh/TerrariumPI/wiki/FAQ#how-to-restart-terrariumpi Now clear your browser cache and reload the webinterface. A brand new version should be running. ## Hardware -This software requires a Raspberry Pi and some extra hardware in order to run and work. The bare minimun is +This software requires a Raspberry Pi and some extra hardware in order to run and work. The bare minimun and tested hardware is - Raspberry PI - Pi 2 - Pi 3 From e3ec221d0a244dc983ff82271b6bbd4ccfc51d04 Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Tue, 24 Oct 2017 22:43:54 +0200 Subject: [PATCH 23/34] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9595f4369..4ddf5f801 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ And all this is controlled with a nice webinterface based on [Gentelella a Boots - Rain control based on humidity sensors and timers - Heater control based on temperature sensors or timers - Cooling control based on temperature sensors or timers -- Weather forecast from external source for lighting schema (turing on when sun rises, turning off when sun sets) +- Weather forecast from external source for lighting schema - Temperatures in Celsius or Fahrenheit - Alarm detections From 4c99caa10b76ca3edc68645e0417f5b48ad757dc Mon Sep 17 00:00:00 2001 From: TheYOSH Date: Tue, 24 Oct 2017 23:08:22 +0200 Subject: [PATCH 24/34] Add extra dimming timing for small changes --- static/js/terrariumpi.js | 3 ++- terrariumConfig.py | 3 +++ terrariumEngine.py | 4 ++++ terrariumSwitch.py | 20 +++++++++++++++----- terrariumTranslations.py | 1 + views/switch_settings.tpl | 13 ++++++++----- 6 files changed, 33 insertions(+), 11 deletions(-) diff --git a/static/js/terrariumpi.js b/static/js/terrariumpi.js index 6fdaaf405..01d7f1a4e 100644 --- a/static/js/terrariumpi.js +++ b/static/js/terrariumpi.js @@ -1122,6 +1122,7 @@ function add_switch() { form.find('input[name="switch_[nr]_name"]').val(), form.find('input[name="switch_[nr]_power_wattage"]').val(), form.find('input[name="switch_[nr]_water_flow"]').val(), + form.find('input[name="switch_[nr]_dimmer_duration"]').val(), form.find('input[name="switch_[nr]_dimmer_on_duration"]').val(), form.find('input[name="switch_[nr]_dimmer_on_percentage"]').val(), form.find('input[name="switch_[nr]_dimmer_off_duration"]').val(), @@ -1131,7 +1132,7 @@ function add_switch() { $('.new-switch-form').modal('hide'); } -function add_switch_row(id,hardwaretype,address,name,power_wattage,water_flow, dimmer_on_duration,dimmer_on_percentage,dimmer_off_duration,dimmer_off_percentage) { +function add_switch_row(id,hardwaretype,address,name,power_wattage,water_flow, dimmer_duration,dimmer_on_duration,dimmer_on_percentage,dimmer_off_duration,dimmer_off_percentage) { var switch_row = $($('.modal-body div.row.switch').parent().clone().html().replace(/\[nr\]/g, $('form div.row.switch').length)); switch_row.find('div.power_switch.small').attr('id','switch_' + id); diff --git a/terrariumConfig.py b/terrariumConfig.py index 4ca5206df..70f6acc32 100644 --- a/terrariumConfig.py +++ b/terrariumConfig.py @@ -298,6 +298,9 @@ def save_power_switch(self,data): del(data['current_water_flow']) if data['hardwaretype'] != 'pwm-dimmer': + if 'dimmer_duration' in data: + del(data['dimmer_duration']) + if 'dimmer_off_duration' in data: del(data['dimmer_off_duration']) diff --git a/terrariumEngine.py b/terrariumEngine.py index 4179fa90f..97cf1a6cf 100644 --- a/terrariumEngine.py +++ b/terrariumEngine.py @@ -128,6 +128,8 @@ def __load_power_switches(self,reloading = False): self.power_switches[power_switch_id].set_power_wattage(switch_config[power_switch_config]['power_wattage']) self.power_switches[power_switch_id].set_water_flow(switch_config[power_switch_config]['water_flow']) + if 'dimmer_duration' in switch_config[power_switch_config]: + self.power_switches[power_switch_id].set_dimmer_duration(switch_config[power_switch_config]['dimmer_duration']) if 'dimmer_on_duration' in switch_config[power_switch_config]: self.power_switches[power_switch_id].set_dimmer_on_duration(switch_config[power_switch_config]['dimmer_on_duration']) if 'dimmer_on_percentage' in switch_config[power_switch_config]: @@ -423,6 +425,8 @@ def set_switches_config(self, data): power_switch.set_name(switchdata['name']) power_switch.set_power_wattage(switchdata['power_wattage']) power_switch.set_water_flow(switchdata['water_flow']) + if 'dimmer_duration' in switchdata: + power_switch.set_dimmer_duration(switchdata['dimmer_duration']) if 'dimmer_on_duration' in switchdata: power_switch.set_dimmer_on_duration(switchdata['dimmer_on_duration']) if 'dimmer_on_percentage' in switchdata: diff --git a/terrariumSwitch.py b/terrariumSwitch.py index b492cdca4..14bf2c157 100644 --- a/terrariumSwitch.py +++ b/terrariumSwitch.py @@ -35,7 +35,7 @@ class terrariumSwitch(): "all":"FF" } - def __init__(self, id, hardware_type, address, name = '', power_wattage = 0.0, water_flow = 0.0, dimmer_on_duration = 0.0, dimmer_on_percentage = 100.0, dimmer_off_duration = 0.0, dimmer_off_percentage = 0.0, callback = None): + def __init__(self, id, hardware_type, address, name = '', power_wattage = 0.0, water_flow = 0.0, dimmer_duration = 5.0, dimmer_on_duration = 0.0, dimmer_on_percentage = 100.0, dimmer_off_duration = 0.0, dimmer_off_percentage = 0.0, callback = None): self.id = id self.callback = callback @@ -53,6 +53,7 @@ def __init__(self, id, hardware_type, address, name = '', power_wattage = 0.0, w self.set_power_wattage(power_wattage) self.set_water_flow(water_flow) + self.set_dimmer_duration(dimmer_duration) self.set_dimmer_on_duration(dimmer_on_duration) self.set_dimmer_on_percentage(dimmer_on_percentage) self.set_dimmer_off_duration(dimmer_off_duration) @@ -88,13 +89,13 @@ def __load_pwm_device(self): logger.error('PiGPIOd process is not running') self.__pigpio = False - def __dim_switch(self,from_value,to_value): + def __dim_switch(self,from_value,to_value,duration): # When the dimmer is working, ignore new state changes. if not self.__dimmer_running: self.__pigpio.set_pull_up_down(int(self.get_address()), pigpio.PUD_OFF) self.__dimmer_running = True - if from_value is None: + if from_value is None or duration == 0: logger.info('Switching dimmer \'%s\' from %s%% to %s%% instantly', self.get_name(),from_value,to_value) # Geen animatie, gelijk to_value @@ -104,7 +105,6 @@ def __dim_switch(self,from_value,to_value): from_value = float(from_value) to_value = float(to_value) direction = (1.0 if from_value < to_value else -1.0) - duration = (self.get_dimmer_on_duration() if direction == 1.0 else self.get_dimmer_off_duration()) logger.info('Changing dimmer \'%s\' from %s%% to %s%% in %s seconds',self.get_name(),from_value,to_value,duration) @@ -167,14 +167,17 @@ def set_state(self, state, force = False): GPIO.output(int(self.get_address()), ( GPIO.LOW if state is terrariumSwitch.ON else GPIO.HIGH )) elif self.get_hardware_type() == 'pwm-dimmer' and self.__pigpio is not False: + duration = self.get_dimmer_duration() # State 100 = full on which means 0 dim. # State is inverse of dim if state is terrariumSwitch.ON: state = self.get_dimmer_on_percentage() + duration = self.get_dimmer_on_duration() elif state is terrariumSwitch.OFF or not (0 <= state <= 100): state = self.get_dimmer_off_percentage() + duration = self.get_dimmer_off_duration() - thread.start_new_thread(self.__dim_switch, (self.state,state)) + thread.start_new_thread(self.__dim_switch, (self.state,state,duration)) self.state = state if self.get_hardware_type() != 'pwm-dimmer': @@ -198,6 +201,7 @@ def get_data(self): 'water_flow' : self.get_water_flow(), 'current_water_flow' : self.get_current_water_flow(), 'state' : self.get_state(), + 'dimmer_duration': self.get_dimmer_duration(), 'dimmer_on_duration': self.get_dimmer_on_duration(), 'dimmer_on_percentage' : self.get_dimmer_on_percentage(), 'dimmer_off_duration': self.get_dimmer_off_duration(), @@ -306,6 +310,12 @@ def dim(self,value): if 0 <= value <= 100: self.set_state(100 - value) + def set_dimmer_duration(self,value): + self.__dimmer_duration = float(value if float(value) >= 0.0 else 0) + + def get_dimmer_duration(self): + return (self.__dimmer_duration if self.get_hardware_type() == 'pwm-dimmer' else 0.0) + def set_dimmer_on_duration(self,value): self.__dimmer_on_duration = float(value if float(value) >= 0.0 else 0) diff --git a/terrariumTranslations.py b/terrariumTranslations.py index 37830595a..1ab88983f 100644 --- a/terrariumTranslations.py +++ b/terrariumTranslations.py @@ -40,6 +40,7 @@ def __load(self): self.translations['switch_field_name'] = _('Holds the switch name.') self.translations['switch_field_power_wattage'] = _('Holds the switch power usage in Watt when switched on.') self.translations['switch_field_water_flow'] = _('Holds the switch water flow in liters per minute when switched on') + self.translations['switch_field_dimmer_duration'] = _('Holds the amount of seconds for the duration in which the dimmer changes to the new value.') self.translations['switch_field_dimmer_on_duration'] = _('Holds the amount of seconds for the duration in which it increases the power.') self.translations['switch_field_dimmer_on_percentage'] = _('Holds the amount in percentage to go to when switched on.') self.translations['switch_field_dimmer_off_duration'] = _('Holds the amount of seconds for the duration in which it decresses the power.') diff --git a/views/switch_settings.tpl b/views/switch_settings.tpl index 9088c8a73..b43dd4214 100644 --- a/views/switch_settings.tpl +++ b/views/switch_settings.tpl @@ -106,18 +106,22 @@