def send_webhook(self,subject,message,files = []): url = subject.decode() webhook = terrariumUtils.parse_url(url) if not webhook: return try: message = ','.join(message.decode().split('\n')) message = '{' + message.replace(':False',':false').replace(':True',':true').replace('None','null').replace('\'','"') + '}' message = json.loads(message) if len(files) > 0: message['files'] = [] for attachment in files: with open(attachment,'rb') as fp: attachment_data = fp.read() message['files'].append({'name' : os.path.basename(attachment), 'data' : b64encode(attachment_data).decode('utf-8')}) headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} r = requests.post(url, data=json.dumps(message), headers=headers) if r.status_code != 200: print('Error sending webhook to url \'\' with status code: {}'.format(url,r.status_code)) except Exception as ex: print('send_webhook exception:') print(ex)
def __checker(self): logger.info('Start terrariumPI door checker for door \'%s\'' % self.get_name()) while True: current_status = None if self.get_hardware_type() == 'gpio': current_status = terrariumDoor.OPEN if GPIO.input(terrariumUtils.to_BCM_port_number(self.get_address())) else terrariumDoor.CLOSED elif self.get_hardware_type() == 'remote' and (int(time.time()) - self.__last_check) >= terrariumDoor.REMOTE_TIMEOUT: current_status = None url_data = terrariumUtils.parse_url(self.get_address()) if url_data is False: logger.error('Remote url \'%s\' for door \'%s\' is not a valid remote source url!' % (self.get_address(),self.get_name())) else: data = terrariumUtils.get_remote_data(self.get_address()) if data is not None: current_status = terrariumDoor.OPEN if terrariumUtils.is_true(data) else terrariumDoor.CLOSED else: logger.warning('Remote door \'%s\' got error from remote source \'%s\'' % (self.get_name(),self.get_address())) self.__last_check = int(time.time()) logger.debug('Current door \'%s\' status: %s' % (self.get_name(),current_status)) if current_status != self.get_status(): logger.info('Door \'%s\' changed from %s to %s' % (self.get_name(),self.get_status(), current_status)) self.set_status(current_status) if self.callback is not None: self.callback(self.get_data()) sleep(terrariumDoor.CHECKER_TIMEOUT)
def update(self): if 'remote' in self.get_hardware_type(): url_data = terrariumUtils.parse_url(self.get_address()) if url_data is False: logger.error('Remote url \'%s\' for switch \'%s\' is not a valid remote source url!' % (self.get_address(),self.get_name())) else: try: data = requests.get(self.get_address(),auth=(url_data['username'],url_data['password']),timeout=3) if data.status_code == 200: data = data.json() json_path = url_data['fragment'].split('/') if 'fragment' in url_data and url_data['fragment'] is not None else [] for item in json_path: # Dirty hack to process array data.... try: item = int(item) except Exception, ex: item = str(item) data = data[item] if 'remote' == self.get_hardware_type(): self.set_state(terrariumUtils.is_true(data)) elif 'remote-dimmer' == self.get_hardware_type(): self.set_state(int(data)) else: logger.warning('Remote switch \'%s\' got error from remote source \'%s\':' % (self.get_name(),self.get_address(),data.status_code))
def get_hardware_state(self): data = None url_data = terrariumUtils.parse_url(self.get_address()) if url_data is False: logger.error('Remote url \'%s\' for switch \'%s\' is not a valid remote source url!' % (self.get_address(),self.get_name())) else: data = terrariumUtils.get_remote_data(self.get_address()) return terrariumPowerSwitch.ON if terrariumUtils.is_true(data) else terrariumPowerSwitch.OFF
def __load_history_data(self): # Onecall API's are more expensive (max 1000 a day - 1 call per 2 minutes) so we update this at a lower frequency # Here we can do 1 hit a day. As the history is per hole full day at a time, and will not change anymore if self.__history_day is not None and self.__history_day == int( datetime.utcfromtimestamp( int(datetime.now().timestamp()) + self._data["timezone"]).strftime('%d')): return True start = time() self._data['history'] = [] address = terrariumUtils.parse_url(self.address) for day in range(1, 3): now = int(datetime.now().timestamp()) + self._data["timezone"] - ( day * 24 * 60 * 60) history_url = 'https://api.openweathermap.org/data/2.5/onecall/timemachine?lat={}&lon={}&units=metric&dt={}&appid={}&lang={}'.format( self._data['geo']['lat'], self._data['geo']['long'], now, address['query_params']['appid'], self._device['language'][0:2]) data = terrariumUtils.get_remote_data(history_url) if data is None: continue for item in data['hourly']: self._data['history'].append({ 'timestamp': int(item["dt"] + self._data["timezone"]), 'temperature': float(item['temp']), 'humidity': float(item['humidity']), 'pressure': float(item['pressure']), 'uvi': float(item['uvi']), }) self._data['history'] = sorted(self._data['history'], key=lambda d: d['timestamp']) self.__history_day = int( datetime.utcfromtimestamp( int(datetime.now().timestamp()) + self._data["timezone"]).strftime('%d')) logger.info( f'Loaded new historical weather data ({len(self._data["history"])} measurements) from {datetime.fromtimestamp(int(self._data["history"][0]["timestamp"]))} till {datetime.fromtimestamp(int(self._data["history"][len(self._data["history"])-1]["timestamp"]))} in {time()-start:.2f} seconds.' ) return True
def send_webhook(self, subject, message, files=[]): url = subject.decode() webhook = terrariumUtils.parse_url(url) if not webhook: return try: message = ','.join(message.decode().split('\n')) message = '{' + message.replace(':False', ':false').replace( ':True', ':true').replace('\'', '"') + '}' message = json.loads(message) headers = { 'Content-type': 'application/json', 'Accept': 'text/plain' } r = requests.post(url, data=json.dumps(message), headers=headers) if r.status_code != 200: print('Error sending webhook to url \'\' with status code: {}'. format(url, r.status_code)) except Exception as ex: print('send_webhook exception:') print(ex)
def __init__(self, url): self.__url = None self.__value = None if terrariumUtils.parse_url(url) is not False: self.__url = url
def update(self, force=False): now = datetime.datetime.now() if now - self.last_update > datetime.timedelta( seconds=terrariumSensor.UPDATE_TIMEOUT) or force: logger.debug( 'Updating %s %s sensor \'%s\'' % (self.get_hardware_type(), self.get_type(), self.get_name())) old_current = self.get_current() current = None try: starttime = time.time() if 'remote' == self.get_hardware_type(): url_data = terrariumUtils.parse_url(self.get_address()) if url_data is False: logger.error( 'Remote url \'%s\' for sensor \'%s\' is not a valid remote source url!' % (self.get_address(), self.get_name())) else: data = terrariumUtils.get_remote_data( self.get_address()) if data is not None: current = float(data) else: logger.warning( 'Remote sensor \'%s\' got error from remote source \'%s\': %s' % (self.get_name(), self.get_address(), data.status_code)) elif 'hc-sr04' == self.get_hardware_type(): GPIO.output( terrariumUtils.to_BCM_port_number( self.sensor_address['TRIG']), False) time.sleep(2) GPIO.output( terrariumUtils.to_BCM_port_number( self.sensor_address['TRIG']), True) time.sleep(0.00001) GPIO.output( terrariumUtils.to_BCM_port_number( self.sensor_address['TRIG']), False) pulse_start = time.time() while GPIO.input( terrariumUtils.to_BCM_port_number( self.sensor_address['ECHO'])) == 0: pulse_start = time.time() pulse_end = time.time() while GPIO.input( terrariumUtils.to_BCM_port_number( self.sensor_address['ECHO'])) == 1: pulse_end = time.time() pulse_duration = pulse_end - pulse_start # https://www.modmypi.com/blog/hc-sr04-ultrasonic-range-sensor-on-the-raspberry-pi # Measure in centimetre current = round(pulse_duration * 17150, 2) elif 'sku-sen0161' == self.get_hardware_type(): # Do multiple measurements... values = [] for counter in range(5): analog_port = MCP3008(channel=int(self.get_address())) # https://github.com/theyosh/TerrariumPI/issues/108 # We measure the values in volts already, so no deviding by 1000 as original script does values.append((analog_port.value * (5000.0 / 1024.0)) * 3.3 + 0.1614) time.sleep(0.2) # sort values from low to high values.sort() # Calculate average. Exclude the min and max value. And therefore devide by 3 current = round((sum(values[1:-1]) / 3.0), 2) elif 'temperature' == self.get_type(): if self.get_hardware_type() == 'owfs': current = float(self.sensor.temperature) elif self.get_hardware_type() == 'w1': data = '' with open( terrariumSensor.W1_BASE_PATH + self.get_address() + '/w1_slave', 'r') as w1data: data = w1data.read() w1data = terrariumSensor.W1_TEMP_REGEX.search(data) if w1data: # Found data current = float(w1data.group('value')) / 1000 elif self.get_hardware_type( ) in terrariumSensor.VALID_DHT_SENSORS.keys(): time.sleep(2.1) humidity, temperature = self.sensor.read_retry( terrariumSensor.VALID_DHT_SENSORS[ self.get_hardware_type()], float( terrariumUtils.to_BCM_port_number( self.sensor_address)), 5) if temperature is not None: current = float(temperature) elif 'humidity' == self.get_type(): if self.get_hardware_type() == 'owfs': current = float(self.sensor.humidity) elif self.get_hardware_type() == 'w1': # Not tested / No hardware to test with pass elif self.get_hardware_type( ) in terrariumSensor.VALID_DHT_SENSORS.keys(): time.sleep(2.1) humidity, temperature = self.sensor.read_retry( terrariumSensor.VALID_DHT_SENSORS[ self.get_hardware_type()], float( terrariumUtils.to_BCM_port_number( self.sensor_address)), 5) if humidity is not None: current = float(humidity) if current is None or not (self.get_limit_min() <= current <= self.get_limit_max()): # Invalid current value.... log and ingore logger.warn( 'Measured value %s%s from %s sensor \'%s\' is outside valid range %.2f%s - %.2f%s in %.5f seconds.' % (current, self.get_indicator(), self.get_type(), self.get_name(), self.get_limit_min(), self.get_indicator(), self.get_limit_max(), self.get_indicator(), time.time() - starttime)) else: self.current = current self.last_update = now logger.info( 'Updated %s sensor \'%s\' from %.2f%s to %.2f%s in %.5f seconds' % (self.get_type(), self.get_name(), old_current, self.get_indicator(), self.get_current(), self.get_indicator(), time.time() - starttime)) except Exception, ex: print ex logger.exception( 'Error updating %s %s sensor \'%s\' with error:' % (self.get_hardware_type(), self.get_type(), self.get_name()))
def update(self, force=False): now = datetime.datetime.now() if now - self.last_update > datetime.timedelta( seconds=terrariumSensor.UPDATE_TIMEOUT) or force: logger.debug( 'Updating %s %s sensor \'%s\'' % (self.get_hardware_type(), self.get_type(), self.get_name())) old_current = self.get_current() current = None try: starttime = time.time() if 'remote' == self.get_hardware_type(): url_data = terrariumUtils.parse_url(self.get_address()) if url_data is False: logger.error( 'Remote url \'%s\' for sensor \'%s\' is not a valid remote source url!' % (self.get_address(), self.get_name())) else: data = requests.get(self.get_address(), auth=(url_data['username'], url_data['password']), timeout=3) if data.status_code == 200: data = data.json() json_path = url_data['fragment'].split( '/') if 'fragment' in url_data and url_data[ 'fragment'] is not None else [] for item in json_path: # Dirty hack to process array data.... try: item = int(item) except Exception, ex: item = str(item) data = data[item] current = float(data) else: logger.warning( 'Remote sensor \'%s\' got error from remote source \'%s\': %s' % (self.get_name(), self.get_address(), data.status_code)) elif 'temperature' == self.get_type(): if self.get_hardware_type() == 'owfs': current = float(self.sensor.temperature) elif self.get_hardware_type() == 'w1': data = '' with open( terrariumSensor.W1_BASE_PATH + self.get_address() + '/w1_slave', 'r') as w1data: data = w1data.read() w1data = terrariumSensor.W1_TEMP_REGEX.search(data) if w1data: # Found data current = float(w1data.group('value')) / 1000 elif self.get_hardware_type( ) in terrariumSensor.VALID_DHT_SENSORS.keys(): humidity, temperature = self.sensor.read_retry( terrariumSensor.VALID_DHT_SENSORS[ self.get_hardware_type()], float( terrariumUtils.to_BCM_port_number( self.sensor_address)), 5) if temperature is not None: current = float(temperature) elif 'humidity' == self.get_type(): if self.get_hardware_type() == 'owfs': current = float(self.sensor.humidity) elif self.get_hardware_type() == 'w1': # Not tested / No hardware to test with pass elif self.get_hardware_type( ) in terrariumSensor.VALID_DHT_SENSORS.keys(): humidity, temperature = self.sensor.read_retry( terrariumSensor.VALID_DHT_SENSORS[ self.get_hardware_type()], float( terrariumUtils.to_BCM_port_number( self.sensor_address)), 5) if humidity is not None: current = float(humidity) if current is None or not (self.get_limit_min() <= current <= self.get_limit_max()): # Invalid current value.... log and ingore logger.warn( 'Measured value %s%s from %s sensor \'%s\' is outside valid range %.2f%s - %.2f%s in %.5f seconds.' % (current, self.get_indicator(), self.get_type(), self.get_name(), self.get_limit_min(), self.get_indicator(), self.get_limit_max(), self.get_indicator(), time.time() - starttime)) else: self.current = current self.last_update = now logger.info( 'Updated %s sensor \'%s\' from %.2f%s to %.2f%s in %.5f seconds' % (self.get_type(), self.get_name(), old_current, self.get_indicator(), self.get_current(), self.get_indicator(), time.time() - starttime))
def _load_hardware(self): if terrariumUtils.parse_url(self.address): return self.address raise terrariumRelayLoadingException(f'Invalid url for relay {self}')
def __load_forecast_data(self): # Onecall API's are more expensive (max 1000 a day - 1 call per 2 minutes) so we update this at a lower frequency address = terrariumUtils.parse_url(self.address) data = terrariumUtils.get_remote_data( 'https://api.openweathermap.org/data/2.5/onecall?lat={}&lon={}&units=metric&exclude=minutely&appid={}&lang={}' .format(self._data['geo']['lat'], self._data['geo']['long'], address['query_params']['appid'], self._device['language'][0:2])) if data: self._data['days'] = [] self._data['forecast'] = [] for hourly in data['hourly']: self._data['forecast'].append({ 'timestamp': int(hourly["dt"] + self._data["timezone"]), 'temperature': float(hourly['temp']), 'humidity': float(hourly['humidity']), }) day_periods = { 'morn': -6 * 60 * 60, 'day': 0, 'eve': 6 * 60 * 60, 'night': 12 * 60 * 60 } for daily in data['daily']: for period in day_periods: timestamp = int(daily["dt"] + self._data["timezone"] + day_periods[period]) # Exlude already existing and pasted hourly forecasts if timestamp not in [ item['timestamp'] for item in self._data['forecast'] ] and timestamp > self._data['forecast'][0]['timestamp']: self._data['forecast'].append({ 'timestamp': timestamp, 'temperature': float(daily['temp'][period]), 'humidity': float(daily['humidity']), }) if 'day' == period: # Store day data for icons day = daily if len(self._data['days']) == 0: # First day, we use the current data day = data['current'] day['temp'] = {period: data['current']['temp']} self._data['days'].append({ 'timestamp': timestamp, 'rise': int(day['sunrise'] + self._data["timezone"]), 'set': int(day['sunset'] + self._data["timezone"]), 'temp': float(day['temp'][period]), 'humidity': float(day['humidity']), 'wind': { 'speed': float(day['wind_speed'] ), # Speed is in meter per second 'direction': float(day['wind_deg']) }, 'weather': { 'description': day['weather'][0].get('description', ''), 'icon': day['weather'][0].get('icon', '') } }) self._data['forecast'] = sorted(self._data['forecast'], key=lambda d: d['timestamp']) self._data['days'] = sorted(self._data['days'], key=lambda d: d['timestamp']) return True return False