| @ -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 | |||