initial commit

Signed-off-by: Sagi Dayan <sagidayan@gmail.com>
This commit is contained in:
Sagi Dayan 2023-01-31 14:34:52 +02:00
commit 7c36d8eea3
9 changed files with 354 additions and 0 deletions

28
README.md Normal file
View file

@ -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 <a href="mailto:picoclock@sagi.dayanhub.com">ZeGoomba</a>.

15
access_point.py Normal file
View file

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

85
index.html Normal file
View file

@ -0,0 +1,85 @@
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
</head>
<body onload="startTime()">
<header>
<h1>PicoClock</h1>
<p>Ayala & Ido's Clock Settings</p>
<p> State: <b><span id="time"></span></b> | 🌞Day: <span class="{day_light_class}">💡</span> | 🌚Night: <span
class="{night_light_class}">💡</span></p>
<nav> <a href="#" onclick="setTime()">🕰️ Set Time</a> <a href="#" onclick="toggle('day')">🌞 Toggle</a> <a
href="#" onclick="toggle('night')">🌚 Toggle</a> </nav>
</header>
<main>
<p> Working Mode: </p>
<input type="radio" id="auto" value="auto" {mode_auto} onclick="update_mode('auto')">
<label for="auto">Schedule</label>
<br>
<input type="radio" id="manual" value="manual" {mode_manual} onclick="update_mode('manual')">
<label for="manual">Manual</label><br>
<hr>
<div style="display: flex;">
<div style="width: 100%;">
<p>🌞 💡Is set to: {day_on_time}</p>
</div>
</div>
<div style="display:flex"> <input type="time" id="N2D" style="width:100%"> <button type="foo"
onclick="set_time_update('day', 'N2D')">SET</button> </div>
<div style="display: flex;">
<div style="width: 100%;">
<p>🌚💡Is set to: {night_on_time}</p>
</div>
</div>
<div style="display:flex"> <input type="time" id="D2N" style="width: 100%;"> <button type="foo"
onclick="set_time_update('night', 'D2N')">SET</button> </div>
</main>
<footer>
<p>Made with ❤️ by <a href="mailto:picoclock@sagi.dayanhub.com">ZeGoomba</a>. v0.0.1</p>
</footer>
</body>
<script>
function toggle(time) {
fetch(`/toggle_${time}/`).finally(reload).catch(console.error);
}
function setTime() {
d = new Date();
fetch(`/stime/${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}/`).finally(reload).catch(console.error);
}
function update_mode(mode) {
fetch(`/update_mode/${mode}/`).finally(reload).catch(console.error);
}
function set_time_update(time, id) {
let el = document.getElementById(id);
let [h, m] = el.value.split(':').map(v => parseInt(v));
if (h && m) fetch(`/set_${time}_time/${h}/${m}/`).finally(reload).catch(console.error);
}
function startTime() {
let time = new Date();
time.setHours({time_h});
time.setMinutes({time_m});
time.setSeconds({time_s});
timeLoop(time);
}
function timeLoop(time) {
let h = time.getHours();
let m = time.getMinutes();
let s = time.getSeconds();
m = checkTime(m);
s = checkTime(s);
document.getElementById('time').innerHTML = h + ":" + m + ":" + s;
setTimeout(timeLoop, 1000, new Date(time.getTime() + 1000));
}
function checkTime(i) {
if (i < 10) {i = "0" + i};
return i;
}
function reload() {
location.reload();
}
</script>
</html>

103
io_state.py Normal file
View file

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

14
main.py Normal file
View file

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

BIN
picoclock.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
screenshot_web.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

13
style.css Normal file
View file

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

96
webserver.py Normal file
View file

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