commit 7c36d8eea34d525ee83ebf034c102adf135209d6 Author: Sagi Dayan Date: Tue Jan 31 14:34:52 2023 +0200 initial commit Signed-off-by: Sagi Dayan diff --git a/README.md b/README.md new file mode 100644 index 0000000..f54deeb --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# πŸ•° PicoClock + +### Ayala & Ido's Clock + + +![PicoClock in the dark](picoclock.jpg "picoclock") + +#### Configuration +- Connect to the PicoClock's WiFi network (SSID `PicoClock`, Password: ********) +- Open your browser and head over to `http://192.168.4.1` +- From there you should sync the PicoClock time with yout phone/laptop by clicking on the `Set Time` button + +Then you can configure time for day/night or manually toggle the lights as you please. + +#### RaspberryPi Pico setup +This setup is very stright forward. PIO's used: +##### Day Time + - PIO.. +##### Night time + - PIO.. + +And that's it. To add/change PIO please take a look [here](https://git.dayanhub.com/sagi/pico-w-clock/src/branch/main/io_state.py#L16). Simply add the pins to the arrays `self._day_pins` and `self._night_pins` + +![screenshot of the web interface](screenshot_web.jpg "Web client") + + +Made with ❀️ by ZeGoomba. + diff --git a/access_point.py b/access_point.py new file mode 100644 index 0000000..c8ec3b1 --- /dev/null +++ b/access_point.py @@ -0,0 +1,15 @@ +import network #importing network +import gc + + +gc.collect() +ssid = 'PicoClock' #Set access point name +password = 'ayalaido' #Set your access point password + +ap = network.WLAN(network.AP_IF) +ap.config(essid=ssid, password=password) +ap.active(True) #activating + +print('AccessPoint Up And Running!') +print(ap.ifconfig()) + diff --git a/index.html b/index.html new file mode 100644 index 0000000..ccb732c --- /dev/null +++ b/index.html @@ -0,0 +1,85 @@ + + + + + + + + +
+

PicoClock

+

Ayala & Ido's Clock Settings

+

State: | 🌞Day: πŸ’‘ | 🌚Night: πŸ’‘

+ +
+
+

Working Mode:

+ + +
+ +
+
+
+
+

🌞 πŸ’‘Is set to: {day_on_time}

+
+
+
+
+
+

πŸŒšπŸ’‘Is set to: {night_on_time}

+
+
+
+
+
+

Made with ❀️ by ZeGoomba. v0.0.1

+
+ + + + diff --git a/io_state.py b/io_state.py new file mode 100644 index 0000000..aa36bda --- /dev/null +++ b/io_state.py @@ -0,0 +1,103 @@ +import machine + +rtc = machine.RTC() + +class State: + def __init__(self): + self._auto_mode = False + self._day_on = False + self._day_on_time = [6, 30] + self._day_off_time = [19, 0] + rtc.datetime((2018, 10, 15, 1, 13, 11, 0, 0)) #(year, month, day, weekday, hours, minutes, seconds, subseconds) + self._current_time = rtc.datetime() + self._current_time = (2018, 20, 15, 1, 13, 11, 0, 0) + self._night_on = False + + self._day_pins = [ + machine.Pin("LED", machine.Pin.OUT) + ] + self._night_pins = [] + for pin in self._day_pins + self._night_pins: + pin.off() + + def is_day_on(self): + return self._day_on + + def is_night_on(self): + return self._night_on + + def is_auto_mode(self): + return self._auto_mode + + def set_auto_mode(self, is_set): + self._auto_mode = is_set + + def toggle_day(self): + print("=> Toggle day") + for pin in self._day_pins: + pin.toggle() + self._day_on = not self._day_on + + def toggle_night(self): + print("=> Toggle night") + for pin in self._night_pins: + pin.toggle() + self._night_on = not self._night_on + + def set_time(self, h, m, s): + rtc.datetime((2018, 10, 15, 1, h, m, s, 0)) + + def set_times(self, is_day, h, m): + if is_day: + self._day_on_time = [h, m] + else: + self._day_off_time = [h, m] + + def switch_to_day(self, is_day): + if is_day: + if not self._day_on: + self.toggle_day() + if self._night_on: + self.toggle_night() + else: + if self._day_on: + self.toggle_day() + if not self._night_on: + self.toggle_night() + + def get_time_h(self): + return self._current_time[4] + + def get_time_m(self): + return self._current_time[5] + + def get_time_s(self): + return self._current_time[6] + + def get_day_on_time(self): + return self._day_on_time + + def get_day_off_time(self): + return self._day_off_time + + def update(self): + self._current_time = rtc.datetime() + if self._auto_mode: + if self.is_day_time(): + self.switch_to_day(True) + else: + self.switch_to_day(False) + else: + pass + + def is_day_time(self): + c_h = self._current_time[4] + c_m = self._current_time[5] + day_h = self._day_on_time[0] + day_m = self._day_on_time[1] + night_h = self._day_off_time[0] + night_m = self._day_off_time[1] + return day_h < c_h < night_h or (day_h == c_h and day_m <= c_m) or (night_h == c_h and c_m < night_m) + +state = State() + diff --git a/main.py b/main.py new file mode 100644 index 0000000..7a61817 --- /dev/null +++ b/main.py @@ -0,0 +1,14 @@ +import machine + +import io_state +import access_point +from webserver import accept_connection + + +try: + while True: + accept_connection() + io_state.state.update() +except KeyboardInterrupt: + machine.reset() + diff --git a/picoclock.jpg b/picoclock.jpg new file mode 100644 index 0000000..f6bf0c9 Binary files /dev/null and b/picoclock.jpg differ diff --git a/screenshot_web.jpg b/screenshot_web.jpg new file mode 100644 index 0000000..76126a3 Binary files /dev/null and b/screenshot_web.jpg differ diff --git a/style.css b/style.css new file mode 100644 index 0000000..9f269b7 --- /dev/null +++ b/style.css @@ -0,0 +1,13 @@ +:root{--sans-font:-apple-system,BlinkMacSystemFont,"Avenir Next",Avenir,"Nimbus Sans L",Roboto,Noto,"Segoe UI",Arial,Helvetica,"Helvetica Neue",sans-serif;--mono-font:Consolas,Menlo,Monaco,"Andale Mono","Ubuntu Mono",monospace;--base-fontsize:1.15rem;--header-scale:1.25;--line-height:1.618;--bg:#fff;--accent-bg:#f5f7ff;--text:#212121;--text-light:#585858;--border:#d8dae1;--accent:#0d47a1;--accent-light:#90caf9;--code:#d81b60;--preformatted:#444;--marked:#ffdd33;--disabled:#efefef}@media (prefers-color-scheme:dark){:root{--bg:#212121;--accent-bg:#2b2b2b;--text:#dcdcdc;--text-light:#ababab;--border:#666;--accent:#ffb300;--accent-light:#ffecb3;--code:#f06292;--preformatted:#ccc;--disabled:#111}}html{font-family:var(--sans-font)}body{color:var(--text);background:var(--bg);font-size:var(--base-fontsize);line-height:var(--line-height);display:flex;min-height:100vh;flex-direction:column;flex:1;margin:0 auto;max-width:45rem;padding:0 .5rem;overflow-x:hidden;word-break:break-word;overflow-wrap:break-word}header{background:var(--accent-bg);border-bottom:1px solid var(--border);text-align:center;padding:2rem .5rem;width:100vw;position:relative;box-sizing:border-box;left:50%;right:50%;margin-left:-50vw;margin-right:-50vw}header h1,header p{margin:0}main{padding-top:1.5rem}h1,h2,h3{line-height:1.1}nav{font-size:1rem;line-height:2;padding:1rem 0}nav a{margin:1rem 1rem 0 0;border:1px solid var(--border);border-radius:5px;color:var(--text)!important;display:inline-block;padding:.1rem 1rem;text-decoration:none;transition:.4s}nav a:hover{color:var(--accent)!important;border-color:var(--accent)}footer{margin-top:4rem;padding:2rem 1rem 1.5rem 1rem;color:var(--text-light);font-size:.9rem;text-align:center;border-top:1px solid var(--border)}h1{font-size:calc(var(--base-fontsize) * var(--header-scale) * var(--header-scale) * var(--header-scale) * var(--header-scale));margin-top:calc(var(--line-height) * 1.5rem)}h2{font-size:calc(var(--base-fontsize) * var(--header-scale) * var(--header-scale) * var(--header-scale));margin-top:calc(var(--line-height) * 1.5rem)}h3{font-size:calc(var(--base-fontsize) * var(--header-scale) * var(--header-scale));margin-top:calc(var(--line-height) * 1.5rem)}h4{font-size:calc(var(--base-fontsize) * var(--header-scale));margin-top:calc(var(--line-height) * 1.5rem)}h5{font-size:var(--base-fontsize);margin-top:calc(var(--line-height) * 1.5rem)}h6{font-size:calc(var(--base-fontsize)/ var(--header-scale));margin-top:calc(var(--line-height) * 1.5rem)}a,a:visited{color:var(--accent)}a:hover{text-decoration:none}[role=button],a button,button,input[type=button],input[type=reset],input[type=submit]{border:none;border-radius:5px;background:var(--accent);font-size:1rem;color:var(--bg);padding:.7rem .9rem;margin:.5rem 0;transition:.4s}[role=button][aria-disabled=true],a button[disabled],button[disabled],input[type=button][disabled],input[type=checkbox][disabled],input[type=radio][disabled],input[type=reset][disabled],input[type=submit][disabled]{cursor:default;opacity:.5;cursor:not-allowed}input:disabled{cursor:not-allowed;background-color:var(--disabled)}input[type=range]{padding:0}[role=button]:focus,[role=button]:not([aria-disabled=true]):hover,button:enabled:hover,button:focus,input[type=button]:enabled:hover,input[type=button]:focus,input[type=checkbox]:enabled:hover,input[type=checkbox]:focus,input[type=radio]:enabled:hover,input[type=radio]:focus,input[type=reset]:enabled:hover,input[type=reset]:focus,input[type=submit]:enabled:hover,input[type=submit]:focus{filter:brightness(1.4);cursor:pointer}input{font-size:inherit;font-family:inherit;padding:.5rem;margin-bottom:.5rem;color:var(--text);background:var(--bg);border:1px solid var(--border);border-radius:5px;box-shadow:none;box-sizing:border-box;width:60%;-moz-appearance:none;-webkit-appearance:none;appearance:none}input[type=checkbox],input[type=radio]{vertical-align:bottom;position:relative}input[type=radio]{border-radius:100%}input[type=checkbox]:checked,input[type=radio]:checked{background:var(--accent)}input[type=checkbox]:checked::after{content:" ";width:.1em;height:.25em;border-radius:0;position:absolute;top:.05em;left:.18em;background:0 0;border-right:solid var(--bg) .08em;border-bottom:solid var(--bg) .08em;font-size:1.8em;transform:rotate(45deg)}input[type=radio]:checked::after{content:" ";width:.25em;height:.25em;border-radius:100%;position:absolute;top:.125em;background:var(--bg);left:.125em;font-size:32px}@media only screen and (max-width:720px){input{width:100%}}input[type=checkbox],input[type=radio]{width:auto}input[type=file]{border:0}hr{color:var(--border);border-top:1px;margin:1rem auto}button{width:100%;margin:0 5px 5px 5px}.grayscale{filter:grayscale(1)} +nav a{ + margin: 0; +} +footer { + margin-top: 1.5rem; +} +main { + padding-top: 0; +} +* { + font-family: "Roboto", "Apple Color Emoji", 'Noto Sans Symbols 2', Helvetica, Arial, sans-serif; +} diff --git a/webserver.py b/webserver.py new file mode 100644 index 0000000..49f1fe9 --- /dev/null +++ b/webserver.py @@ -0,0 +1,96 @@ +import re +try: + import usocket as socket #importing socket +except: + import socket + +from io_state import state + +css_file = open('style.css', 'r') +css = css_file.read() +css_file.close() + +html_file = open("index.html", "r") +html = html_file.read() +html_file.close() + + + +def web_page(): + return html \ + .replace("{day_light_class}", "" if state.is_day_on() else "grayscale") \ + .replace("{night_light_class}", "" if state.is_night_on() else "grayscale") \ + .replace("{time_h}", str(state.get_time_h())) \ + .replace("{time_m}", str(state.get_time_m())) \ + .replace("{time_s}", str(state.get_time_s())) \ + .replace("{mode_auto}", "checked" if state.is_auto_mode() else "") \ + .replace("{mode_manual}", "" if state.is_auto_mode() else "checked") \ + .replace("{day_on_time}", f"{state.get_day_on_time()[0]:02}:{state.get_day_on_time()[1]:02}") \ + .replace("{night_on_time}", f"{state.get_day_off_time()[0]:02}:{state.get_day_off_time()[1]:02}") \ + + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #creating socket object +s.bind(('0.0.0.0', 80)) +s.listen(5) +s.settimeout(0.5) + +def accept_connection(): + try: + global s + conn, addr = s.accept() + request = str(conn.recv(1024)) + + if request.find('/style.css') >= 0: + conn.send(css) + conn.close() + return + elif request.find('/update_mode/auto/') >= 0: + print("Update mode to auto.") + state.set_auto_mode(True) + elif request.find('/update_mode/manual/') >= 0: + print("Update mode to manual.") + state.set_auto_mode(False) + + elif request.find('/stime/') >= 0: + timestamp_regex = re.compile(r'stime\/(\d+):(\d+):(\d+)\/') + h = int(timestamp_regex.search(request).group(1)) + m = int(timestamp_regex.search(request).group(2)) + _s = int(timestamp_regex.search(request).group(3)) + print(f"Changing current time to: {h}:{m}:{_s}") + state.set_time(h, m, _s) + + elif request.find('/toggle_day/') >= 0: + print("toggle day") + state.set_auto_mode(False) + state.toggle_day() + elif request.find('/toggle_night/') >= 0: + print("toggle night.") + state.set_auto_mode(False) + state.toggle_night() + + elif request.find('/set_day_time/') >= 0: + times_regex = re.compile(r'time\/(\d+)\/(\d+)/') + h = int(times_regex.search(request).group(1)) + m = int(times_regex.search(request).group(2)) + print(f'Changing time for day_on to {h}:{m}') + state.set_times(True, h, m) + elif request.find('/set_night_time/') >= 0: + times_regex = re.compile(r'time\/(\d+)\/(\d+)/') + h = int(times_regex.search(request).group(1)) + m = int(times_regex.search(request).group(2)) + print(f'Changing time for day_off to {h}:{m}') + state.set_times(False, h, m) + + response = web_page() + conn.send(response) + conn.close() + except OSError as e: + try: + conn.close() + print(f'ERROR: connection closed: {e}') + except: + pass + except Exception as e: + pass + +