Last active
February 19, 2022 21:00
-
-
Save walthowd/7a0529b6b3481af5736ea2e63e27b059 to your computer and use it in GitHub Desktop.
USPS Mail Notifications with Home Assistant and Node-RED - based off https://skalavala.github.io/usps/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
homeassistant: | |
customize: | |
sensor.usps_mail: | |
friendly_name: USPS Mail | |
sensor.usps_packages: | |
friendly_name: USPS Packages | |
group: | |
USPS: | |
entities: | |
- sensor.usps_mail | |
- sensor.usps_packages | |
- camera.usps_mail_pictures | |
sensor: | |
- platform: mqtt | |
name: 'USPS Mail' | |
state_topic: '/usps/mails' | |
value_template: "{{ value }}" | |
- platform: mqtt | |
name: USPS Packages | |
state_topic: '/usps/packages' | |
value_template: "{{ value }}" | |
camera: | |
- platform: generic | |
name: USPS Mail Pictures | |
still_image_url: 'http://192.168.xxx.xxx:8123/local/todays_mails.gif' | |
shell_command: | |
usps_mail_check: '/usr/bin/python3 /home/homeassistant/.homeassistant/scripts/usps-nodered.py' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{"id":"56a1dbb3.88e2a4","type":"comment","z":"20661f8a.532c4","name":"Check USPS for mail and notify","info":"","x":150,"y":420,"wires":[]},{"id":"77de95d0.9df38c","type":"api-call-service","z":"20661f8a.532c4","name":"Notify Family","server":"e2a02faf.48099","service_domain":"notify","service":"family_iphones","data":"{ \"message\": \"msg\" }","mergecontext":"","x":950,"y":540,"wires":[[]]},{"id":"666694a4.04b05c","type":"api-call-service","z":"20661f8a.532c4","name":"Logbook entry","server":"e2a02faf.48099","service_domain":"logbook","service":"log","data":"{ \"name\":\"Node-Red\",\"message\":\"Sent today's mail notification to family iphones\" }","mergecontext":"","x":960,"y":500,"wires":[[]]},{"id":"8bf2b54e.9bfec8","type":"api-call-service","z":"20661f8a.532c4","name":"Check USPS for mail","server":"e2a02faf.48099","service_domain":"shell_command","service":"usps_mail_check","data":"{ }","mergecontext":"","x":980,"y":460,"wires":[[]]},{"id":"d79af930.fc13a8","type":"function","z":"20661f8a.532c4","name":"Format message","func":"msg.payload = { data: {'message': 'Today\\'s Mail', data: { attachment: {'url': 'https://yourname.duckdns.org:/local/todays_mails.gif' } } } };\nreturn msg;","outputs":1,"noerr":0,"x":730,"y":500,"wires":[["457de064.1d725","77de95d0.9df38c","666694a4.04b05c"]]},{"id":"76a6783a.3e4358","type":"delay","z":"20661f8a.532c4","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"12","rateUnits":"hour","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":520,"y":500,"wires":[["d79af930.fc13a8"]]},{"id":"f85bfdbd.8ce55","type":"switch","z":"20661f8a.532c4","name":"Getting mail today?","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"num"},{"t":"eq","v":"0","vt":"num"}],"checkall":"true","repair":false,"outputs":2,"x":310,"y":500,"wires":[["76a6783a.3e4358"],[]],"outputLabels":["mail today",""]},{"id":"257bd907.8646d6","type":"server-state-changed","z":"20661f8a.532c4","name":"USPS Mail Count","server":"e2a02faf.48099","entityidfilter":"sensor.usps_mail","entityidfiltertype":"exact","haltifstate":"","x":100,"y":500,"wires":[["f85bfdbd.8ce55"]]},{"id":"764b0022.fd7d8","type":"inject","z":"20661f8a.532c4","name":"Every five minutes in the morning","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"*/5 7-9 * * *","once":false,"onceDelay":"","x":180,"y":460,"wires":[["8bf2b54e.9bfec8"]]},{"id":"e2a02faf.48099","type":"server","z":"","name":"Home Assistant","url":"https://localhost:8123","pass":"XXXX"}] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Make sure you change the parameters - username, password, mailbox, | |
paths and options. | |
""" | |
import email | |
import datetime, imaplib, re, sys | |
import os | |
import time | |
import subprocess | |
import paho.mqtt.client as mosquitto | |
from shutil import copyfile | |
# MQTT Server Address and Port | |
MQTT_SERVER = "127.0.0.1" | |
MQTT_SERVER_PORT = 1883 | |
# MQTT User name and Password | |
MQTT_USERNAME = "username" | |
MQTT_PASSWORD = "password" | |
MQTT_USPS_MAIL_TOPIC = "/usps/mails" | |
MQTT_USPS_PACKAGE_TOPIC = "/usps/packages" | |
SLEEP_TIME_IN_SECONDS = 300 | |
HOST = 'imap.gmail.com' | |
PORT = 993 | |
USERNAME = 'user@email.com' | |
PASSWORD = 'password' | |
folder = 'inbox' | |
GIF_FILE_NAME = "todays_mails.gif" | |
GIF_MAKER_OPTIONS = '/usr/bin/convert -delay 300 -loop 0 ' | |
IMAGE_OUTPUT_PATH = '/home/homeassistant/.homeassistant/www/' | |
# Login Method | |
############################################################################### | |
def login(): | |
account = imaplib.IMAP4_SSL(HOST, PORT) | |
try: | |
rv, data = account.login(USERNAME, PASSWORD) | |
print_message ("Logged into your email server successfully!") | |
except imaplib.IMAP4.error: | |
print_message ('Failed to authenticate using the given credentials. Check your username, password, host and port.') | |
sys.exit(1) | |
return account | |
# Select folder inside the mailbox | |
############################################################################### | |
def selectFolder(account, folder): | |
rv, mailboxes = account.list() | |
rv, data = account.select(folder) | |
print_message ("Selecting folder '{}'".format(folder)) | |
# Creates GIF image based on the attachments in the inbox | |
############################################################################### | |
def get_mails(account): | |
today = get_formatted_date() | |
image_count = 0 | |
rv, data = account.search ( None, | |
'(FROM "USPS" SUBJECT "Informed Delivery Daily Digest" SINCE "' + | |
today + '")') | |
if rv == 'OK': | |
for num in data[0].split(): | |
rv, data = account.fetch(num, '(RFC822)') | |
msg = email.message_from_string(data[0][1].decode('utf-8')) | |
images = [] | |
for part in msg.walk(): | |
if part.get_content_maintype() == "multipart": | |
continue | |
if part.get('Content-Disposition') is None: | |
continue | |
filepath = IMAGE_OUTPUT_PATH + part.get_filename() | |
fp = open( filepath, 'wb' ) | |
fp.write(part.get_payload(decode=True)) | |
images.append(filepath) | |
image_count = image_count + 1 | |
fp.close() | |
print_message ('Found {} mails and images in your email.'.format(image_count)) | |
if image_count > 0: | |
all_images = "" | |
for image in images: | |
all_images = all_images + image + " " | |
print_message ("Creating animated GIF out of {} images.".format(image_count)) | |
os.system( GIF_MAKER_OPTIONS + all_images + | |
IMAGE_OUTPUT_PATH + GIF_FILE_NAME ) | |
print_message ("Cleaning up...") | |
for image in images: | |
os.remove(image) | |
if (image_count == 0): | |
print_message("Found '{}' mails".format(image_count)) | |
return image_count | |
# Returns today in specific format | |
############################################################################### | |
def get_formatted_date(): | |
return datetime.datetime.today().strftime('%d-%b-%Y') | |
# gets packages count | |
############################################################################### | |
def package_count(account): | |
count = 0 | |
today = get_formatted_date() | |
rv, data = account.search(None, | |
'(FROM "auto-reply@usps.com" SUBJECT "Item Delivered" SINCE "' + | |
today + '")') | |
if rv == 'OK': | |
count = len(data[0].split()) | |
print_message("Found '{}' packages".format(count)) | |
return count | |
# Prints message to console | |
############################################################################### | |
def print_message(message): | |
print("{} USPS: {}".format(datetime.datetime.today().strftime('%d-%b-%Y %H:%m:%S%p'), message)) | |
# OnConnect Callback | |
############################################################################### | |
def on_connect(mosq, userdata, flags, rc): | |
print_message("Connected with return code: {}".format(str(rc))) | |
# OnLog Callback | |
############################################################################### | |
def on_log(mosq, obj, level, string): | |
print_message(string) | |
# Primary logic for the component starts here | |
############################################################################### | |
# Primary logic for the component starts here | |
############################################################################### | |
try: | |
# create a new MQTT Client Object | |
mqttc = mosquitto.Mosquitto() | |
# Set event callbacks | |
mqttc.on_connect = on_connect | |
# Uncomment below line to enable debug/console messages | |
# mqttc.on_log = on_log | |
# Connect to MQTT using the username/password set above | |
mqttc.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) | |
mqttc.connect(MQTT_SERVER, MQTT_SERVER_PORT) | |
print_message ("Connected to MQTT Server successfully") | |
except Exception as ex: | |
print_message ("Error connecting to MQTT.") | |
print_message (str(ex)) | |
sys.exit(1) | |
try: | |
account = login() | |
selectFolder(account, folder) | |
except Exception as exx: | |
print_message ("Error connecting logging into email server.") | |
print_message (str(exx)) | |
sys.exit(1) | |
# Get the mail count and drop it in the MQTT | |
mc = get_mails(account) | |
mqttc.publish(MQTT_USPS_MAIL_TOPIC, str(mc), qos=0, retain=False) | |
# Get the package count and drop it in the MQTT | |
pc = package_count(account) | |
mqttc.publish(MQTT_USPS_PACKAGE_TOPIC, str(pc), qos=0, retain=False) | |
# if there are no mails, make sure you delete the old file, | |
# so that the next day, you don't see yesterday's mails | |
# when there are no mails, copy nomail.jpg as your default file | |
if mc == 0: | |
os.remove(IMAGE_OUTPUT_PATH + GIF_FILE_NAME) | |
copyfile(IMAGE_OUTPUT_PATH + "nomail.gif", IMAGE_OUTPUT_PATH + GIF_FILE_NAME) | |
# disconnect from MQTT | |
mqttc.disconnect() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment