Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add foreground mode to aid systemd service #155

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
3 changes: 3 additions & 0 deletions bin/smarthome.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ def reload_logics():
arggroup.add_argument('-q', '--quiet', help='reduce logging to the logfile', action='store_true')
arggroup.add_argument('-V', '--version', help='show SmartHome.py version', action='store_true')
arggroup.add_argument('--start', help='start SmartHome.py and detach from console (default)', default=True, action='store_true')
arggroup.add_argument('-f', '--foreground', help='start SmartHome.py and stay in foreground', action='store_true')
args = argparser.parse_args()

if args.interactive:
Expand Down Expand Up @@ -612,6 +613,8 @@ def reload_logics():
LOGLEVEL = logging.WARNING
elif args.verbose:
LOGLEVEL = logging.DEBUG
elif args.foreground:
MODE = 'foreground'

# check for pid file
pid = lib.daemon.get_pid(__file__)
Expand Down
191 changes: 131 additions & 60 deletions plugins/enocean/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ def _rocker_sequence(self, item, sender_id, sequence):
except Exception as e:
logger.error("enocean: error handling enocean_rocker_sequence \"{}\" - {}".format(sequence, e))

def _multivalue_txdelay(self, item, id_offset, delay):
try:
time.sleep(0.3)
if ('RED' in item._enocean_txdl_data) and ('GREEN' in item._enocean_txdl_data) and ('BLUE' in item._enocean_txdl_data):
self.send_4bs([7 << 4, item._enocean_txdl_data['BLUE'], item._enocean_txdl_data['GREEN'], item._enocean_txdl_data['RED']], id_offset)
logger.debug("enocean: tx delay for item {} with {}".format(item, item._enocean_txdl_data))
except Exception as e:
logger.error("enocean: error handling tx delay for item {} - {}".format(item, e))

def _process_packet_type_radio(self, data, optional):
#logger.warning("enocean: processing radio message with data = [{}] / optional = [{}]".format(', '.join(['0x%02x' % b for b in data]), ', '.join(['0x%02x' % b for b in optional])))

Expand Down Expand Up @@ -367,57 +376,108 @@ def parse_item(self, item):
self._rx_items[rx_id][rx_eep].append(item)

logger.info("enocean: item {} listens to id {:08X} with eep {} key {}".format(item, rx_id, rx_eep, rx_key))
#logger.info("enocean: self._rx_items = {}".format(self._rx_items))

if 'enocean_tx_key' in item.conf:
# look for info from the most specific info to the broadest (key->eep->id) - one id might use multiple eep might define multiple keys
eep_item = item
while (not 'enocean_tx_eep' in eep_item.conf):
eep_item = eep_item.return_parent()
if (eep_item is self._sh):
logger.error("enocean: could not find enocean_tx_eep for item {}".format(item))
return None
id_item = eep_item
while (not 'enocean_tx_id' in id_item.conf):
id_item = id_item.return_parent()
if (id_item is self._sh):
logger.error("enocean: could not find enocean_tx_id for item {}".format(item))
return None
return self.update_item
return None

def update_item(self, item, caller=None, source=None, dest=None):
if caller != 'EnOcean':
logger.debug('enocean: item updated externally')
if self._block_ext_out_msg:
logger.debug('enocean: sending manually blocked by user. Aborting')
if caller == 'EnOcean':
return
logger.debug('enocean: item updated externally')
if self._block_ext_out_msg:
logger.debug('enocean: sending manually blocked by user. Aborting')
return
tx_key = item.conf['enocean_tx_key']
eep_item = item
while (not 'enocean_tx_eep' in eep_item.conf):
eep_item = eep_item.return_parent()
if (eep_item is self._sh):
logger.error("enocean: could not find enocean_tx_eep for item {}".format(item))
return
if 'enocean_tx_eep' in item.conf:
if isinstance(item.conf['enocean_tx_eep'], str):
tx_eep = item.conf['enocean_tx_eep']
logger.debug('enocean: item has tx_eep')
id_offset = 0
if 'enocean_tx_id_offset' in item.conf and (isinstance(item.conf['enocean_tx_id_offset'], str)):
logger.debug('enocean: item has valid enocean_tx_id_offset')
id_offset = int(item.conf['enocean_tx_id_offset'])
#if (isinstance(item(), bool)):
#if item.conf['type'] == bool:
#Identify send command based on tx_eep coding:
if(tx_eep == 'A5_38_08_02'):
#if isinstance(item, bool):
logger.debug('enocean: item is A5_38_08_02 type')
if not item():
self.send_dim(id_offset, 0, 0)
logger.debug('enocean: sent off command')
else:
if 'ref_level' in item.level.conf:
dim_value = int(item.level.conf['ref_level'])
logger.debug('enocean: ref_level found')
else:
dim_value = 100
logger.debug('enocean: no ref_level found. Setting to default 100')
self.send_dim(id_offset, dim_value, 0)
logger.debug('enocean: sent dim on command')
elif(tx_eep == 'A5_38_08_03'):
logger.debug('enocean: item is A5_38_08_03 type')
self.send_dim(id_offset, item(), 0)
logger.debug('enocean: sent dim command')
elif(tx_eep == 'A5_38_08_01'):
logger.debug('enocean: item is A5_38_08_01 type')
self.send_switch(id_offset, item(), 0)
logger.debug('enocean: sent switch command')
else:
logger.error('enocean: error: Unknown tx eep command')
tx_eep = eep_item.conf['enocean_tx_eep']
id_item = eep_item
while (not 'enocean_tx_id' in id_item.conf):
id_item = id_item.return_parent()
if (id_item is self._sh):
logger.error("enocean: could not find enocean_tx_id for item {}".format(item))
return
tx_id = id_item.conf['enocean_tx_id']
id_offset = 0
if 'enocean_tx_id_offset' in item.conf and (isinstance(item.conf['enocean_tx_id_offset'], str)):
logger.debug('enocean: item has valid enocean_tx_id_offset')
id_offset = int(item.conf['enocean_tx_id_offset'])
#if (isinstance(item(), bool)):
#if item.conf['type'] == bool:
#Identify send command based on tx_eep coding:
if (tx_eep == 'A5_38_09'):
#logger.debug('enocean: item is A5_38_09 type')
if (tx_key.upper()) in ['RED', 'GREEN', 'BLUE']:
#logger.debug('enocean: rgb-type')
if ('enocean_tx_delay' in item.conf) and (float(item.conf['enocean_tx_delay']) > 0.0):
if not hasattr(eep_item, '_enocean_txdl_thread') or not eep_item._enocean_txdl_thread.isAlive():
eep_item._enocean_txdl_data = {'RED': 0, 'GREEN': 0, 'BLUE': 0}
eep_item._enocean_txdl_thread = threading.Thread(target=self._multivalue_txdelay, name="enocean-txdl", args=(eep_item, id_offset, float(item.conf['enocean_tx_delay']), ))
logger.info("starting enocean tx delay thread for eep_item {}".format(eep_item))
eep_item._enocean_txdl_thread.daemon = True
eep_item._enocean_txdl_thread.start()
eep_item._enocean_txdl_data[tx_key.upper()] = item()
else:
logger.error('enocean: tx_eep is not a string value')
data = [0, 0, 0, 0]
data[0] = 7 << 4
for rgb_item in item.return_parent().return_children():
if (not 'enocean_tx_key' in rgb_item.conf):
continue
if (rgb_item.conf['enocean_tx_key'].upper() == 'RED'):
data[3] = rgb_item()
logger.debug('enocean: found red-value')
elif (rgb_item.conf['enocean_tx_key'].upper() == 'GREEN'):
data[2] = rgb_item()
logger.debug('enocean: found green-value')
elif (rgb_item.conf['enocean_tx_key'].upper() == 'BLUE'):
data[1] = rgb_item()
logger.debug('enocean: found blue-value')
self.send_4bs(data, id_offset)
elif (tx_eep == 'A5_38_08_02'):
#if isinstance(item, bool):
logger.debug('enocean: item is A5_38_08_02 type')
if not item():
self.send_dim(id_offset, 0, 0)
logger.debug('enocean: sent off command')
else:
logger.debug('enocean: item has no tx_eep value')
if 'ref_level' in item.level.conf:
dim_value = int(item.level.conf['ref_level'])
logger.debug('enocean: ref_level found')
else:
dim_value = 100
logger.debug('enocean: no ref_level found. Setting to default 100')
self.send_dim(id_offset, dim_value, 0)
logger.debug('enocean: sent dim on command')
elif (tx_eep == 'A5_38_08_03'):
logger.debug('enocean: item is A5_38_08_03 type')
self.send_dim(id_offset, item(), 0)
logger.debug('enocean: sent dim command')
elif (tx_eep == 'A5_38_08_01'):
logger.debug('enocean: item is A5_38_08_01 type')
self.send_switch(id_offset, item(), 0)
logger.debug('enocean: sent switch command')
else:
logger.error('enocean: error: Unknown tx eep command')

def read_num_securedivices(self):
def read_num_securedevices(self):
self._send_common_command(CO_RD_NUMSECUREDEVICES)
logger.info("enocean: Read number of secured devices")

Expand Down Expand Up @@ -479,56 +539,67 @@ def _send_common_command(self, _code, data=[], optional=[]):
self._response_lock.release()
self._cmd_lock.release()

def _send_radio_packet(self, id_offset, _code, data=[], optional=[]):
def _send_radio_packet(self, _code, data, optional=[], id_offset = 0):
if (id_offset < 0) or (id_offset > 127):
logger.error("enocean: invalid base ID offset range. (Is {}, must be [0 127])".format(id_offset))
return
self._cmd_lock.acquire()
self._last_cmd_code = SENT_RADIO_PACKET
self._send_packet(PACKET_TYPE_RADIO, [_code] + data + list((self.tx_id + id_offset).to_bytes(4, byteorder='big')) + [0x00], optional)
self._send_packet(PACKET_TYPE_RADIO, [_code] + list(reversed(data)) + list((self.tx_id + id_offset).to_bytes(4, byteorder='big')) + [0x00], optional)
self._response_lock.acquire()
# wait 5sec for response
self._response_lock.wait(5)
self._response_lock.release()
self._cmd_lock.release()

def send_dim(self,id_offset=0, dim=0, dimspeed=0):
def send_4bs(self, data, id_offset = 0):
if (len(data) != 4):
logger.error("enocean: supply all 4 data bytes!")
return
self._send_radio_packet(0xA5, data, id_offset = id_offset)

def send_learn_4bs(self, _func, _type, _manufacturer_id = 0x7ff, id_offset = 0):
if (_func > 0x3f) or (_type > 0x7f) or (_manufacturer_id > 0x7ff):
logger.error("enocean: learn - invalid params!")
return
data = [0, 0, 0, 0]
data[0] = 0x80
data[1] = _manufacturer_id & 0xff
data[2] = (_manufacturer_id >> 8) + ((_type & 0x1f) << 3)
data[3] = (_type >> 5) + (_func << 2)
self.send_4bs(data, id_offset)

def send_dim(self, dim = 0, dimspeed = 0, id_offset = 0):
if (dimspeed < 0) or (dimspeed > 255):
logger.error("enocean: sending dim command A5_38_08: invalid range of dimspeed")
return
logger.debug("enocean: sending dim command A5_38_08")
if (dim == 0):
self._send_radio_packet(id_offset, 0xA5, [0x02, 0x00, dimspeed, 0x08])
self.send_4bs([0x08, dimspeed, 0x00, 0x02], id_offset)
elif (dim > 0) and (dim <= 100):
self._send_radio_packet(id_offset, 0xA5, [0x02, dim, dimspeed, 0x09])
self.send_4bs([0x09, dimspeed, dim, 0x02], id_offset)
else:
logger.error("enocean: sending command A5_38_08: invalid dim value")

def send_switch(self,id_offset=0, on=0, block=0):
def send_switch(self, on = 0, block = 0, id_offset = 0):
if (block < 0) and (block > 1):
logger.error("enocean: sending switch command A5_38_08: invalid range of block (0,1)")
return
logger.debug("enocean: sending switch command A5_38_08")
if (on == 0):
self._send_radio_packet(id_offset, 0xA5, [0x01, 0x00, 0x00, 0x08])
self.send_4bs([0x08, 0x00, 0x00, 0x01], id_offset)
elif (on == 1) and (block == 0):
self._send_radio_packet(id_offset, 0xA5, [0x01, 0x00, 0x00, 0x09])
self.send_4bs([0x09, 0x00, 0x00, 0x01], id_offset)
else:
logger.error("enocean: sending command A5_38_08: error")

def send_learn_dim(self, id_offset=0):
if (id_offset < 0) or (id_offset > 127):
logger.error("enocean: ID offset out of range (0-127). Aborting.")
return
logger.info("enocean: sending learn telegram for dim command")
self._send_radio_packet(id_offset, 0xA5, [0x02, 0x00, 0x00, 0x00])
self.send_4bs([0x00, 0x00, 0x00, 0x02], id_offset)

def send_learn_switch(self, id_offset=0):
if (id_offset < 0) or (id_offset > 127):
logger.error("enocean: ID offset out of range (0-127). Aborting.")
return
logger.info("enocean: sending learn telegram for switch command")
self._send_radio_packet(id_offset, 0xA5, [0x01, 0x00, 0x00, 0x00])
self.send_4bs([0x00, 0x00, 0x00, 0x01], id_offset)

def _calc_crc8(self, msg, crc=0):
for i in msg:
Expand Down
4 changes: 2 additions & 2 deletions plugins/iaqstick/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ def _init_dev(self, dev):

def run(self):
devs = usb.core.find(idVendor=0x03eb, idProduct=0x2013, find_all=True)
if devs is None:
logger.error('iaqstick: iAQ Stick not found')
if not devs:
logger.error('iaqstick: no iAQ Stick found')
return
logger.debug('iaqstick: {} iAQ Stick connected'.format(len(devs)))
self._intf = 0
Expand Down
87 changes: 85 additions & 2 deletions plugins/mail/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,16 @@
import imaplib
import smtplib
import email
from email.mime.text import MIMEText

import urllib

from email.header import Header
from email import encoders

from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.image import MIMEImage

logger = logging.getLogger('')

Expand Down Expand Up @@ -170,6 +178,81 @@ def __call__(self, to, sub, msg):
except:
pass

def extended(self, to, sub, msg, sender_name: str, img_list: list=[], attachments: list=[]):
try:
smtp = self._connect()
except Exception as e:
logger.warning("Could not connect to {0}: {1}".format(self._host, e))
return
try:
sender_name = Header(sender_name, 'utf-8').encode()
msg_root = MIMEMultipart('mixed')
msg_root['Subject'] = Header(sub, 'utf-8')
msg_root['From'] = email.utils.formataddr((sender_name, self._from))
msg_root['Date'] = email.utils.formatdate(localtime=1)
if not isinstance(to, list):
to = [to]
msg_root['To'] = email.utils.COMMASPACE.join(to)

msg_root.preamble = 'This is a multi-part message in MIME format.'

msg_related = MIMEMultipart('related')
msg_root.attach(msg_related)

msg_alternative = MIMEMultipart('alternative')
msg_related.attach(msg_alternative)

msg_text = MIMEText(msg.encode('utf-8'), 'plain', 'utf-8')
msg_alternative.attach(msg_text)

html = """
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
</head>
<body>
<font face="verdana" size=2>{}<br/></font>
<img src="cid:image0" border=0 />
</body>
</html>
""".format(msg) # template

msg_html = MIMEText(html.encode('utf-8'), 'html', 'utf-8')
msg_alternative.attach(msg_html)

for i, img in enumerate(img_list):
if img.startswith('http://'):
fp = urllib.request.urlopen(img)
else:
fp = open(img, 'rb')
msg_image = MIMEImage(fp.read())
msg_image.add_header('Content-ID', '<image{}>'.format(i))
msg_related.attach(msg_image)

for attachment in attachments:
fname = os.path.basename(attachment)

if attachment.startswith('http://'):
f = urllib.request.urlopen(attachment)
else:
f = open(attachment, 'rb')
msg_attach = MIMEBase('application', 'octet-stream')
msg_attach.set_payload(f.read())
encoders.encode_base64(msg_attach)
msg_attach.add_header('Content-Disposition', 'attachment',
filename=(Header(fname, 'utf-8').encode()))
msg_root.attach(msg_attach)

smtp.send_message(msg_root)
except Exception as e:
logger.warning("Could not send message {} to {}: {}".format(sub, to, e))
finally:
try:
smtp.quit()
del(smtp)
except:
pass

def _connect(self):
smtp = smtplib.SMTP(self._host, self._port)
if self._ssl:
Expand All @@ -191,4 +274,4 @@ def parse_logic(self, logic):
pass

def update_item(self, item, caller=None, source=None, dest=None):
pass
pass
Loading