@ -0,0 +1,120 @@ | |||||
.ampy | |||||
# Phue Creds | |||||
bridge.dat | |||||
# Swap files | |||||
*.sw[a-p] | |||||
# Byte-compiled / optimized / DLL files | |||||
__pycache__/ | |||||
*.py[cod] | |||||
*$py.class | |||||
# C extensions | |||||
*.so | |||||
# Distribution / packaging | |||||
.Python | |||||
build/ | |||||
develop-eggs/ | |||||
dist/ | |||||
downloads/ | |||||
eggs/ | |||||
.eggs/ | |||||
lib/ | |||||
lib64/ | |||||
parts/ | |||||
sdist/ | |||||
var/ | |||||
wheels/ | |||||
*.egg-info/ | |||||
.installed.cfg | |||||
*.egg | |||||
MANIFEST | |||||
# PyInstaller | |||||
# Usually these files are written by a python script from a template | |||||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | |||||
*.manifest | |||||
*.spec | |||||
# Installer logs | |||||
pip-log.txt | |||||
pip-delete-this-directory.txt | |||||
# Unit test / coverage reports | |||||
htmlcov/ | |||||
.tox/ | |||||
.nox/ | |||||
.coverage | |||||
.coverage.* | |||||
.cache | |||||
nosetests.xml | |||||
coverage.xml | |||||
*.cover | |||||
.hypothesis/ | |||||
.pytest_cache/ | |||||
# Translations | |||||
*.mo | |||||
*.pot | |||||
# Django stuff: | |||||
*.log | |||||
local_settings.py | |||||
db.sqlite3 | |||||
# Flask stuff: | |||||
instance/ | |||||
.webassets-cache | |||||
# Scrapy stuff: | |||||
.scrapy | |||||
# Sphinx documentation | |||||
docs/_build/ | |||||
# PyBuilder | |||||
target/ | |||||
# Jupyter Notebook | |||||
.ipynb_checkpoints | |||||
# IPython | |||||
profile_default/ | |||||
ipython_config.py | |||||
# pyenv | |||||
.python-version | |||||
# celery beat schedule file | |||||
celerybeat-schedule | |||||
# SageMath parsed files | |||||
*.sage.py | |||||
# Environments | |||||
.env | |||||
.venv | |||||
env/ | |||||
venv/ | |||||
ENV/ | |||||
env.bak/ | |||||
venv.bak/ | |||||
# Spyder project settings | |||||
.spyderproject | |||||
.spyproject | |||||
# Rope project settings | |||||
.ropeproject | |||||
# mkdocs documentation | |||||
/site | |||||
# mypy | |||||
.mypy_cache/ | |||||
.dmypy.json | |||||
dmypy.json | |||||
# Pyre type checker | |||||
.pyre/ |
@ -0,0 +1,122 @@ | |||||
# MicroPython Sensor Alarm | |||||
# Table of Contents | |||||
<!-- vim-markdown-toc GFM --> | |||||
* [Overview](#overview) | |||||
* [Credits](#credits) | |||||
* [Parts](#parts) | |||||
* [Magnetic Switch - Setup](#magnetic-switch---setup) | |||||
* [Setup](#setup) | |||||
<!-- vim-markdown-toc --> | |||||
# Overview | |||||
This project utilizes a magnetic (Reed) switch to flash a group of Philips Hue lights and/or send an email | |||||
The script is designed to go into deepsleep so that it could be utilized on battery power. It wakes up from the magnetic switch movement, causing the alarm to trigger | |||||
This is one of the my first micropython projects so I combined a lot of things, like Philips Hue and emails together unnecessary. Should have considered modules for each instead. | |||||
# Credits | |||||
* [HueBridge](https://github.com/FRC4564/HueBridge) bridge (named [uhue.py](libary/uhue.py) here) - Small changes made to support RGB colors as well | |||||
* [µMail](https://github.com/shawwwn/uMail) | |||||
# Parts | |||||
| Part | Cost | Quantity | Total | | |||||
| ------------------------------------------------------------------------------------------------------------------------------------ | ---- | -------- | ----- | | |||||
| [Magnetic Switch](https://smile.amazon.com/gp/product/B0735BP1K4/) | 8.98 | 1 | 8.98 | | |||||
| [Project Box](https://smile.amazon.com/gp/product/B0002BBQUA/) | 6.51 | 1 | 6.51 | | |||||
| A ESP32 Controller (I used [Wemos LOLIN D32](https://www.aliexpress.com/item/WEMOS-LOLIN32-V1-0-0-wifi-bluetooth-board-based-ESP-32-4MB-FLASH/32808551116.html)) | 6.50 | 1 | 6.51 | | |||||
| Momentary switch | | 1 | | | |||||
# Magnetic Switch - Setup | |||||
 | |||||
[Fritzing Project](static/wemos-lolin-d32-magnetic_switch.fzz) | |||||
* Note 1: Above schematic has the magentic switch connected at pin 32. This should work although I actually use 16 | |||||
* Note 2: 10kΩ [Pull-up Resistor](https://duino4projects.com/pull-resistors-explained/) | |||||
`GPIO16` has a pull-up resistor that can be enabled. No need for an external resistor. | |||||
I noticed that the board would not boot if the magnetic switch was connect to `GPIO0`. It would only work if I connected the switch after the boot. Using `GPIO16` seems to get around this. | |||||
```python | |||||
from machine import Pin | |||||
p0 = Pin(16, Pin.IN, Pin.PULL_UP) | |||||
``` | |||||
Example Output | |||||
```python | |||||
>>> from machine import Pin | |||||
>>> p0 = Pin(16, Pin.IN, Pin.PULL_UP) | |||||
>>> p0.value() # Open | |||||
1 | |||||
>>> p0.value() # Closed | |||||
0 | |||||
>>> p0.value() # Open | |||||
1 | |||||
>>> p0.value() # Closed | |||||
0 | |||||
``` | |||||
# Setup | |||||
1. Modify [config.py](config.py). Leave those features you don't want commented out (e.g. phue, smtp, button, etc): | |||||
```python | |||||
config = {} | |||||
config["wifi_ssid"] = "MySSID" | |||||
config["wifi_pass"] = "Password" | |||||
config["sensor_pin"] = 4 # Sets Pin.PULL_UP | |||||
config["device_name"] = "Front Door Alarm" # Device sending alert | |||||
config["config_button"] = 23 # Pin containing stop/start button | |||||
config["config_led"] = 5 # Pin with LED | |||||
# Delete/Comment Out If Not Needed | |||||
config["phue"]["group"] = 3 # the light group to flash | |||||
config["phue"]["color1"] = (244, 0, 0) | |||||
config["phue"]["color2"] = (0, 0, 204) | |||||
config["phue"]["flash_count"] = 3 | |||||
config["smtp"]["subject"] = "Front door open" | |||||
config["smtp"]["from"] = "<from_address>" | |||||
config["smtp"]["to"] = "<to_address>" | |||||
config["smtp"]["username"] = "<username>" | |||||
config["smtp"]["password"] = "<password>" | |||||
config["smtp"]["server"] = "smtp.gmail.com" | |||||
config["smtp"]["ssl"] = True | |||||
config["smtp"]["port"] = 465 | |||||
``` | |||||
2. Upload the scripts | |||||
```bash | |||||
ampy --port /dev/ttyUSB0 put library/alarm.py | |||||
ampy --port /dev/ttyUSB0 put library/leds.py | |||||
ampy --port /dev/ttyUSB0 put library/uhue.py | |||||
ampy --port /dev/ttyUSB0 put library/umail.py | |||||
ampy --port /dev/ttyUSB0 put library/wifi.py | |||||
ampy --port /dev/ttyUSB0 put config.py | |||||
ampy --port /dev/ttyUSB0 put main.py | |||||
``` | |||||
3. If first run, you should connect over serial and open the switch. The script should ask for you to press the connect button on the bridge. Additionally, the `bridge.dat` file can be backed up and saved for other sensors that might use them: | |||||
```bash | |||||
ampy --port /dev/ttyUSB0 get bridge.dat | |||||
``` | |||||
which looks like the following: | |||||
```bash | |||||
["<bridge_ip>", "<api_username>"] | |||||
``` |
@ -0,0 +1,7 @@ | |||||
import gc | |||||
import wifi | |||||
status = wifi.connect() | |||||
print(f"Network Settings: {status}") | |||||
gc.collect() # Garbage collection |
@ -0,0 +1,29 @@ | |||||
config = {} | |||||
config["wifi_ssid"] = "" | |||||
config["wifi_pass"] = "" | |||||
config["sensor_pin"] = 4 # Sets Pin.PULL_UP | |||||
config["device_name"] = "Front Door Alarm" # Device sending alert | |||||
# | |||||
# UNCOMMENT AND CONFIGURE FEATURES | |||||
# | |||||
config["config_button"] = 23 # Pin containing stop/start button | |||||
config["config_led"] = 5 # Pin with LED | |||||
config["phue"] = {} | |||||
config["phue"]["group"] = 3 # the light group to flash | |||||
config["phue"]["color1"] = (244, 0, 0) | |||||
config["phue"]["color2"] = (0, 0, 204) | |||||
config["phue"]["flash_count"] = 3 | |||||
config["smtp"] = {} | |||||
config["smtp"]["server"] = "" | |||||
config["smtp"]["ssl"] = True | |||||
config["smtp"]["port"] = 465 | |||||
config["smtp"]["subject"] = "Front door open" | |||||
config["smtp"]["from"] = "" | |||||
config["smtp"]["to"] = "" | |||||
config["smtp"]["username"] = "" | |||||
config["smtp"]["password"] = "" |
@ -0,0 +1,67 @@ | |||||
""" | |||||
Various alarm related functions | |||||
""" | |||||
from time import sleep | |||||
def phue_fetch_light_states(bridge, group_id): | |||||
""" | |||||
Fetch the current states of each light. | |||||
Returns a list of dictionaries {"id": "<id>", "state": {}} | |||||
""" | |||||
lights = [] | |||||
group = bridge.getGroup(group_id) | |||||
for light_id in group["lights"]: | |||||
light = bridge.getLight(light_id) | |||||
lights.append({"id": light_id, "state": light["state"]}) | |||||
return lights | |||||
def phue_flash_lights(bridge, config): | |||||
""" | |||||
Transition between 2 colors | |||||
""" | |||||
transition_time = 2 | |||||
sleep_time = 0.5 | |||||
i = 1 | |||||
while i < config["flash_count"]: | |||||
bridge.setGroup( | |||||
config["group"], rgb=config["color1"], transitiontime=transition_time, on=True | |||||
) | |||||
sleep(sleep_time) | |||||
bridge.setGroup(config["group"], rgb=config["color2"], transitiontime=transition_time) | |||||
sleep(sleep_time) | |||||
i = i + 1 | |||||
def send_email(name, config): | |||||
""" | |||||
Send an email noting an issue | |||||
""" | |||||
import umail | |||||
smtp = umail.SMTP(config["server"], config["port"], ssl=config["ssl"]) | |||||
smtp.login(config["username"], config["password"]) | |||||
smtp.to(config["to"]) | |||||
smtp.write("From: {}\n".format(config["from"])) | |||||
smtp.write("To: {}\n".format(config["to"])) | |||||
smtp.write("Subject: {}\n".format(name)) | |||||
smtp.write("-- Generated by {} --\n".format(__name__)) | |||||
smtp.send() | |||||
smtp.quit() | |||||
smtp = None | |||||
def trigger_alert(name, phue=None, smtp=None): | |||||
""" | |||||
Set off the various alerts | |||||
""" | |||||
if phue: | |||||
import uhue as hue | |||||
bridge = hue.Bridge() | |||||
lights = phue_fetch_light_states(bridge, phue["group"]) | |||||
phue_flash_lights(bridge, phue) | |||||
# Reset light colors | |||||
for light in lights: | |||||
bridge.setLight(light["id"], **light["state"]) | |||||
if smtp: | |||||
send_email(name, smtp) |
@ -0,0 +1,15 @@ | |||||
from machine import Pin | |||||
from time import sleep | |||||
def blink(led_pin, count, delay=0.5): | |||||
led = Pin(led_pin, Pin.OUT) # Usually GPIO2 | |||||
led.value(1) # Start off | |||||
x = 0 | |||||
while x < count*2: | |||||
led.value(not led.value()) | |||||
sleep(delay) | |||||
x = x + 1 | |||||
def test(led_pin=5): | |||||
led_blink(led_pin, 3, delay=0.2) |
@ -0,0 +1,250 @@ | |||||
import socket | |||||
from time import sleep | |||||
import json | |||||
try: | |||||
import requests | |||||
except: | |||||
import urequests as requests | |||||
# UPnP SSDP Search request header | |||||
HEADER = b"""M-SEARCH * HTTP/1.1\r | |||||
HOST: 239.255.255.250:1900\r | |||||
MAN: "ssdp:discover"\r | |||||
ST: ssdp:all\r | |||||
MX: 3\r | |||||
\r | |||||
""" | |||||
def rgb2xy(r, g, b): | |||||
''' | |||||
Converts an RGB value to xyz coordinates, based on matrix transformations | |||||
of colorspace values here (Wide Gamut RGB): | |||||
http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html | |||||
''' | |||||
X = 0.7161046 * r + 0.1009296 * g + 0.1471858 * b | |||||
Y = 0.2581874 * r + 0.7249378 * g + 0.0168748 * b | |||||
Z = 0.0000000 * r + 0.0517813 * g + 0.7734287 * b | |||||
x = X / (X+Y+Z) | |||||
y = Y / (X+Y+Z) | |||||
return [x, y] | |||||
class Bridge: | |||||
"""Provides methods for connecting to and using Hue Bridge. Supports | |||||
Micropython, Python 2, and 3.""" | |||||
def __init__(self,autosetup=True, debug=1): | |||||
self.debug = debug #0=no prints, 1=messages, 2=debug | |||||
self.IP = None | |||||
self.username = None | |||||
if autosetup: | |||||
self.setup() | |||||
def show(self,str,level=1): | |||||
""" Show debug output. """ | |||||
if self.debug >= level: | |||||
print(str) | |||||
def setup(self): | |||||
""" Loads bridge settings or attempts to establish them, if needed.""" | |||||
success = self.loadSettings() | |||||
if success: | |||||
# verify bridge settings work | |||||
try: | |||||
self.idLights() | |||||
success = True | |||||
except: | |||||
success = False | |||||
if not success: | |||||
if self.discover(): | |||||
self.show('Bridge located at {}'.format(self.IP)) | |||||
self.show('>>> Press link button on Hue bridge to register <<<') | |||||
if self.getUsername(): | |||||
success = self.saveSettings() | |||||
else: | |||||
self.show("Couldn't get username from bridge.") | |||||
else: | |||||
self.show("Couldn't find bridge on LAN.") | |||||
return success | |||||
def discover(self): | |||||
""" Locate Hue Bridge IP using UPnP SSDP search. Discovery will return | |||||
when bridge is found or 3 seconds after last device response. Returns IP | |||||
address or None.""" | |||||
#On ESP8266, disable AP WLAN to force use of STA interface | |||||
#import network | |||||
#ap = network.WLAN(network.AP_IF) | |||||
#ap.active(False) | |||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |||||
s.sendto(HEADER, ('239.255.255.250',1900)) #UPnP Multicast | |||||
s.settimeout(3) | |||||
IP = None | |||||
while IP == None: | |||||
data, addr = s.recvfrom(1024) | |||||
self.show(str(data),2) | |||||
lines = data.split(b'\r\n') | |||||
for l in lines: | |||||
tokens = l.split(b' ') | |||||
if tokens[0] == b'SERVER:': | |||||
product = tokens[3].split(b'/') | |||||
if product[0] == b'IpBridge': | |||||
IP = str(addr[0]) | |||||
break | |||||
s.close() | |||||
self.IP = IP | |||||
return IP | |||||
def getUsername(self): | |||||
""" Get a developer API username from bridge. | |||||
Requires that the bridge link button be pressed sometime while polling. | |||||
Polls for 20 seconds (20 attempts at 1 second intervals). | |||||
Can timeout with error if bridge is non-responsive. | |||||
Returns username on success or None on failure.""" | |||||
url = 'http://{}/api'.format(self.IP) | |||||
data = '{"devicetype":"TapLight#mydevice"}' | |||||
username = None | |||||
count = 20 | |||||
while count > 0 and username == None: | |||||
resp = requests.post(url,data=data) | |||||
if resp.status_code == 200: | |||||
j = resp.json()[0] | |||||
self.show(j,2) | |||||
if j.get('success'): | |||||
username = str(j['success']['username']) | |||||
self.username = username | |||||
sleep(1) | |||||
count -= 1 | |||||
return username | |||||
def saveSettings(self): | |||||
""" Save bridge IP and username to bridge.dat file. | |||||
Returns True on success.""" | |||||
if self.IP and self.username: | |||||
f=open('bridge.dat','w') | |||||
f.write(json.dumps([self.IP,self.username])) | |||||
f.close() | |||||
return True | |||||
else: | |||||
return None | |||||
def loadSettings(self): | |||||
""" Load bridge IP and username from bridge.dat file and set base URL. | |||||
Returns True on success. """ | |||||
try: | |||||
f=open('bridge.dat') | |||||
except: | |||||
return None | |||||
l = json.load(f) | |||||
f.close() | |||||
self.IP = str(l[0]) | |||||
self.username = str(l[1]) | |||||
self.show('Loaded settings {} {}'.format(self.IP,self.username),2) | |||||
return True | |||||
def resetSettings(self): | |||||
"""Delete current saved bridge settings and reinitiate.""" | |||||
from os import remove | |||||
remove('bridge.dat') | |||||
self.IP = None | |||||
self.username = None | |||||
self.setup() | |||||
def url(self,path): | |||||
"""Return url for API calls.""" | |||||
return 'http://{}/api/{}/{}'.format(self.IP,self.username,path) | |||||
def get(self, path): | |||||
"""Perform GET request and return json result.""" | |||||
url = self.url(path) | |||||
self.show(url,2) | |||||
resp = requests.get(url).json() | |||||
self.show(resp,2) | |||||
return resp | |||||
def put(self, path, data): | |||||
"""Perform PUT request and return response.""" | |||||
url = self.url(path) | |||||
self.show(url,2) | |||||
data = json.dumps(data) | |||||
self.show(data,2) | |||||
resp = requests.put(url, data=data).json() | |||||
self.show(resp,2) | |||||
return resp | |||||
def allLights(self): | |||||
"""Returns dictionary containing all lights, with detail.""" | |||||
"""Large return set, not ideal for controllers with limited RAM.""" | |||||
return self.get('lights') | |||||
def idLights(self): | |||||
"""Returns list of all light IDs.""" | |||||
ids = self.get('groups/0')['lights'] | |||||
for i in range(len(ids)): | |||||
ids[i] = int(ids[i]) | |||||
return ids | |||||
def getLight(self,id): | |||||
"""Returns dictionary of light details for given ID.""" | |||||
return self.get('lights/{}'.format(str(id))) | |||||
def getLights(self): | |||||
"""Iterates through each light to build and return a dictionary | |||||
of light IDs and names.""" | |||||
dict = {} | |||||
for i in self.idLights(): | |||||
dict[i] = str(self.getLight(i)['name']) | |||||
return dict | |||||
def setLight(self,id,**kwargs): | |||||
"""Set one or more states of a light. | |||||
Ex: setLight(1,on=True,bri=254,hue=50000,sat=254)""" | |||||
if 'rgb' in kwargs: | |||||
r,g,b = kwargs.pop('rgb') | |||||
kwargs['xy'] = rgb2xy(r, g, b) | |||||
self.put('lights/{}/state'.format(str(id)),kwargs) | |||||
def allGroups(self): | |||||
"""Returns dictionary containing all groups, with detail.""" | |||||
return self.get('groups') | |||||
def getGroup(self,id): | |||||
"""Returns dictionary of group details.""" | |||||
return self.get('groups/{}'.format(str(id))) | |||||
def getGroups(self): | |||||
"""Returns dictionary of group IDs and names.""" | |||||
dict = {} | |||||
groups = self.allGroups() | |||||
for g in groups: | |||||
dict[int(g)] = str(groups[g]['name']) | |||||
return dict | |||||
def setGroup(self,id,**kwargs): | |||||
"""Set one or more states of a group. | |||||
Ex: setGroup(1,bri_inc=100,transitiontime=40)""" | |||||
if 'rgb' in kwargs: | |||||
r,g,b = kwargs.pop('rgb') | |||||
kwargs['xy'] = rgb2xy(r, g, b) | |||||
self.put('groups/{}/action'.format(str(id)),kwargs) |
@ -0,0 +1,108 @@ | |||||
# µMail (MicroMail) for MicroPython | |||||
# Copyright (c) 2018 Shawwwn <shawwwn1@gmai.com> | |||||
# License: MIT | |||||
import usocket | |||||
DEFAULT_TIMEOUT = 10 # sec | |||||
LOCAL_DOMAIN = '127.0.0.1' | |||||
CMD_EHLO = 'EHLO' | |||||
CMD_STARTTLS = 'STARTTLS' | |||||
CMD_AUTH = 'AUTH' | |||||
CMD_MAIL = 'MAIL' | |||||
AUTH_PLAIN = 'PLAIN' | |||||
AUTH_LOGIN = 'LOGIN' | |||||
class SMTP: | |||||
def cmd(self, cmd_str): | |||||
sock = self._sock; | |||||
sock.write('%s\r\n' % cmd_str) | |||||
resp = [] | |||||
next = True | |||||
while next: | |||||
code = sock.read(3) | |||||
next = sock.read(1) == b'-' | |||||
resp.append(sock.readline().strip().decode()) | |||||
return int(code), resp | |||||
def __init__(self, host, port, ssl=False, username=None, password=None): | |||||
import ussl | |||||
self.username = username | |||||
addr = usocket.getaddrinfo(host, port)[0][-1] | |||||
sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM) | |||||
sock.settimeout(DEFAULT_TIMEOUT) | |||||
sock.connect(addr) | |||||
if ssl: | |||||
sock = ussl.wrap_socket(sock) | |||||
code = int(sock.read(3)) | |||||
sock.readline() | |||||
assert code==220, 'cant connect to server %d' % code | |||||
self._sock = sock | |||||
code, resp = self.cmd(CMD_EHLO + ' ' + LOCAL_DOMAIN) | |||||
assert code==250, '%d' % code | |||||
if CMD_STARTTLS in resp: | |||||
code, resp = self.cmd(CMD_STARTTLS) | |||||
assert code==220, 'start tls failed, %d' % code | |||||
self._sock = ussl.wrap_socket(sock) | |||||
if username and password: | |||||
self.login(username, password) | |||||
def login(self, username, password): | |||||
self.username = username | |||||
code, resp = self.cmd(CMD_EHLO + ' ' + LOCAL_DOMAIN) | |||||
assert code==250, '%d' % code | |||||
auths = None | |||||
for feature in resp: | |||||
if feature[:4].upper() == CMD_AUTH: | |||||
auths = feature[4:].upper().split() | |||||
assert auths!=None, "no auth method" | |||||
from ubinascii import b2a_base64 as b64 | |||||
if AUTH_PLAIN in auths: | |||||
cren = b64("\0%s\0%s" % (username, password))[:-1].decode() | |||||
code, resp = self.cmd('%s %s %s' % (CMD_AUTH, AUTH_PLAIN, cren)) | |||||
elif AUTH_LOGIN in auths: | |||||
code, resp = self.cmd("%s %s %s" % (CMD_AUTH, AUTH_LOGIN, b64(username)[:-1].decode())) | |||||
assert code==334, 'wrong username %d' % code | |||||
code, resp = self.cmd(b64(password)[:-1]) | |||||
else: | |||||
raise Exception("auth(%s) not supported " % ', '.join(auths)) | |||||
assert code==235 or code==503, 'auth error %d' % code | |||||
return code, resp | |||||
def to(self, addrs): | |||||
code, resp = self.cmd(CMD_EHLO + ' ' + LOCAL_DOMAIN) | |||||
assert code==250, '%d' % code | |||||
code, resp = self.cmd('MAIL FROM: <%s>' % self.username) | |||||
assert code==250, 'sender refused %d' % code | |||||
if isinstance(addrs, str): | |||||
addrs = [addrs] | |||||
count = 0 | |||||
for addr in addrs: | |||||
code, resp = self.cmd('RCPT TO: <%s>' % addr) | |||||
if code!=250 and code!=251: | |||||
print('%s refused' % addr) | |||||
count += 1 | |||||
assert count!=len(addrs), 'recipient refused, %d' % code | |||||
code, resp = self.cmd('DATA') | |||||
assert code==354, 'data refused, %d' % code | |||||
return code, resp | |||||
def write(self, content): | |||||
self._sock.write(content) | |||||
def send(self, content=''): | |||||
if content: | |||||
self.write(content) | |||||
self._sock.write('\r\n.\r\n') # the five letter sequence marked for ending | |||||
line = self._sock.readline() | |||||
return (int(line[:3]), line[4:].strip().decode()) | |||||
def quit(self): | |||||
self.cmd("QUIT") | |||||
self._sock.close() |
@ -0,0 +1,47 @@ | |||||
import network | |||||
try: | |||||
from config import config | |||||
except ImportError: | |||||
config = {} | |||||
ap_if = network.WLAN(network.AP_IF) | |||||
wifi = network.WLAN(network.STA_IF) | |||||
def connect(enable_ap=False): | |||||
ap_if.active(enable_ap) | |||||
if not config.get("wifi_ssid") or not config.get("wifi_pass"): | |||||
print("WARNING: wifi_ssid or wifi_pass not configured in config.py") | |||||
return status() | |||||
if not wifi.isconnected(): | |||||
wifi.active(True) | |||||
wifi.connect(config["wifi_ssid"], config["wifi_pass"]) | |||||
while not wifi.isconnected(): | |||||
pass | |||||
return status() | |||||
def disconnect(): | |||||
wifi = network.WLAN(network.STA_IF) | |||||
wifi.active(False) | |||||
def status(): | |||||
results = { | |||||
"ap": ap_if.ifconfig()[0] != "0.0.0.0", | |||||
"wifi": { | |||||
"ip": "0.0.0.0", | |||||
"netmask": "0.0.0.0", | |||||
"gateway": "0.0.0.0", | |||||
"dns": "0.0.0.0", | |||||
} | |||||
} | |||||
if wifi.isconnected(): | |||||
ifconfig = wifi.ifconfig() | |||||
results["wifi"] = { | |||||
"ip": ifconfig[0], | |||||
"netmask": ifconfig[1], | |||||
"gateway": ifconfig[2], | |||||
"dns": ifconfig[3], | |||||
} | |||||
return results |
@ -0,0 +1,36 @@ | |||||
from machine import deepsleep, Pin, wake_reason | |||||
from esp32 import wake_on_ext0 | |||||
from time import sleep | |||||
from config import config | |||||
from leds import led_blink | |||||
def main(): | |||||
pin_switch = Pin(config['sensor_pin'], Pin.IN, Pin.PULL_UP) | |||||
wake_on_ext0(pin=pin_switch, level=Pin.WAKE_LOW) | |||||
if wake_reason() == 0 and not config.get("config_button"): # Power on - Run on boot | |||||
sleep(10) # Give 10 seconds to cancel from serial | |||||
deepsleep() | |||||
elif wake_reason() == 0 and config.get("config_button"): # Power on - Wait for button before starting | |||||
pin_config = Pin(config["config_button"], Pin.IN, Pin.PULL_UP) | |||||
while True: | |||||
print('Waiting for button...') | |||||
if not pin_config.value(): | |||||
if config.get("config_led"): | |||||
led_blink(config["config_led"], 2, interval=0.2) | |||||
deepsleep() | |||||
sleep(1) | |||||
elif wake_reason() == 1: # Sensor triggered alarm (woke from deep sleep) | |||||
import alarm | |||||
import wifi | |||||
wifi.connect() | |||||
alarm.trigger_alert(config["device_name"], phue=config.get("phue"), smtp=config.get("smtp")) | |||||
deepsleep() | |||||
try: | |||||
main() | |||||
except KeyboardInterrupt: | |||||
pass | |||||
finally: | |||||
led_blink(config["config_led"], 2, interval=0.2) |
@ -0,0 +1,2 @@ | |||||
esptool | |||||
adafruit-ampy |