diff --git a/README.md b/README.md
index 1a244cd..cfdeb92 100644
--- a/README.md
+++ b/README.md
@@ -152,3 +152,8 @@ Let's assume you haven't built the ESP8266 sensor. You can still test your servi
### Libs
- https://github.com/jczic/MicroWebSrv
+
+
+## rshell
+
+rshell is a shell for accessing a mi
\ No newline at end of file
diff --git a/basic_dht/main.py b/basic_dht/main.py
new file mode 100644
index 0000000..f13a7f4
--- /dev/null
+++ b/basic_dht/main.py
@@ -0,0 +1,23 @@
+import time
+
+from dht import DHT11
+from machine import Pin
+
+
+sensor1 = DHT11(Pin(2))
+# sensor2 = DHT11(Pin(2))
+
+
+while True:
+ data = {
+ "sensor_1": {
+ "temp": sensor1.temperature(),
+ "humidity": sensor1.humidity(),
+ },
+ # "sensor_2": {
+ # "temp": sensor2.temperature(),
+ # "humidity": sensor2.humidity(),
+ # },
+ }
+ print(data)
+ time.sleep(3)
diff --git a/iot_power/SPEC.md b/iot_power/SPEC.md
new file mode 100644
index 0000000..71d2370
--- /dev/null
+++ b/iot_power/SPEC.md
@@ -0,0 +1,17 @@
+# Open Water Change Specification
+
+## Startup
+
+On startup the Open Water Change(OWC) should check for a config.json file under the root directory. If found it should parse the contents and attempts to connect to wireless access points in the order that they are listed.
+
+```
+{
+"xor_key": 42,
+access_points: [
+ {
+ "ssid": "whatever",
+ "secret": ""
+ }
+]
+}
+```
\ No newline at end of file
diff --git a/iot_power/boot.py b/iot_power/boot.py
new file mode 100644
index 0000000..b4307c1
--- /dev/null
+++ b/iot_power/boot.py
@@ -0,0 +1,67 @@
+# import json
+# import network
+# import time
+
+
+# # def xor_encrypt_decrypt(input_string, key):
+# # output = "".join(chr(ord(char) ^ key) for char in input_string)
+# # return output
+
+
+# # def wifi_connect(ssid, password, timeout=10):
+# # """
+# # Connects to the specified Wi-Fi access point.
+
+# # :param ssid: The SSID of the Wi-Fi network.
+# # :param password: The password for the Wi-Fi network.
+# # :param timeout: Time in seconds to wait for connection.
+# # :return: True if connected, False otherwise.
+# # """
+# # wlan = network.WLAN(network.STA_IF)
+# # wlan.active(True)
+
+# # if not wlan.isconnected():
+# # print(f"Connecting to network: {ssid}...")
+# # wlan.connect(ssid, password)
+
+# # start_time = time.time()
+# # while not wlan.isconnected():
+# # if time.time() - start_time > timeout:
+# # print("Connection timeout!")
+# # wlan.active(False)
+# # del wlan
+# # return False
+# # time.sleep(1)
+
+# # print("Network connected!")
+# # print("IP Address:", wlan.ifconfig()[0])
+# # return True
+
+
+# def wifi_setup():
+# # try:
+# # with open("config.json", "r") as f:
+# # config = json.load(f)
+
+# # for ap in config.get("access_points", []):
+# # result = wifi_connect(
+# # ap["ssid"], xor_encrypt_decrypt(ap["secret"], config["xor_key"])
+# # )
+# # if result:
+# # return
+
+# # except (OSError, ValueError) as e:
+# # print("OSError config.json unreadable or no")
+
+# print("Falling back on selfhosted AP")
+# ap = network.WLAN(network.AP_IF)
+# ap.config(essid="RP2040-AP-2", password="testing123")
+# ap.ifconfig(("192.168.4.1", "255.255.255.0", "192.168.4.1", "192.168.4.1"))
+# ap.active(True)
+# print("SSID RP2040-AP")
+# return
+
+
+# print("Attempting to connect to wifi")
+# time.sleep(10)
+# wifi_setup()
diff --git a/iot_power/main.py b/iot_power/main.py
new file mode 100644
index 0000000..fe2695f
--- /dev/null
+++ b/iot_power/main.py
@@ -0,0 +1,71 @@
+import network
+import socket
+import machine
+import time
+
+# Setup RP2040 as an access point
+ap = network.WLAN(network.AP_IF)
+ap.config(essid="RP2040-AP-3", password="testing123")
+ap.ifconfig(("192.168.4.1", "255.255.255.0", "192.168.4.1", "192.168.4.1"))
+ap.active(True)
+
+# Initialize onboard LED
+led = machine.Pin("LED", machine.Pin.OUT, value=1)
+motor_ctl_a = machine.Pin(16, machine.Pin.OUT, value=0)
+motor_ctl_b = machine.Pin(17, machine.Pin.OUT, value=0)
+
+# Create a socket and listen on port 80
+addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
+# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s = socket.socket()
+s.bind(addr)
+# only allowed to listen to 5 connections in AP mode
+s.listen(5)
+
+print("listening on", ap.ifconfig()[0])
+
+# HTML to send to browsers
+html = """
+
+
+
+
Toggle Motor
+
+
+
+
+"""
+
+
+def serve():
+ conn, addr = s.accept()
+ print("client connected from", addr)
+ request = conn.recv(1024)
+ request_str = str(request)
+ print("request:", request_str)
+
+ if "POST /toggle" in request_str:
+ led.toggle()
+ motor_ctl_a.toggle() # Toggle LED state
+ conn.send("HTTP/1.1 200 OK\n\nLED Toggled")
+ else:
+ conn.send("HTTP/1.1 200 OK\n")
+ conn.send("Content-Type: text/html\n")
+ conn.send("Connection: close\n\n")
+ conn.sendall(html) # Send HTML page
+ conn.close()
+
+
+# Main loop: accept connections and respond
+for i in range(10):
+ print(f"Preparing to serve...{i}")
+ time.sleep(1)
+
+while True:
+ serve()
diff --git a/open_water_change/SPEC.md b/open_water_change/SPEC.md
new file mode 100644
index 0000000..71d2370
--- /dev/null
+++ b/open_water_change/SPEC.md
@@ -0,0 +1,17 @@
+# Open Water Change Specification
+
+## Startup
+
+On startup the Open Water Change(OWC) should check for a config.json file under the root directory. If found it should parse the contents and attempts to connect to wireless access points in the order that they are listed.
+
+```
+{
+"xor_key": 42,
+access_points: [
+ {
+ "ssid": "whatever",
+ "secret": ""
+ }
+]
+}
+```
\ No newline at end of file
diff --git a/open_water_change/boot.py b/open_water_change/boot.py
new file mode 100644
index 0000000..163ceda
--- /dev/null
+++ b/open_water_change/boot.py
@@ -0,0 +1,67 @@
+import json
+import network
+import time
+
+
+def xor_encrypt_decrypt(input_string, key):
+ output = "".join(chr(ord(char) ^ key) for char in input_string)
+ return output
+
+
+def wifi_connect(ssid, password, timeout=10):
+ """
+ Connects to the specified Wi-Fi access point.
+
+ :param ssid: The SSID of the Wi-Fi network.
+ :param password: The password for the Wi-Fi network.
+ :param timeout: Time in seconds to wait for connection.
+ :return: True if connected, False otherwise.
+ """
+ wlan = network.WLAN(network.STA_IF)
+ wlan.active(True)
+
+ if not wlan.isconnected():
+ print(f"Connecting to network: {ssid}...")
+ wlan.connect(ssid, password)
+
+ start_time = time.time()
+ while not wlan.isconnected():
+ if time.time() - start_time > timeout:
+ print("Connection timeout!")
+ wlan.active(False)
+ del wlan
+ return False
+ time.sleep(1)
+
+ print("Network connected!")
+ print("IP Address:", wlan.ifconfig()[0])
+ return True
+
+
+def wifi_setup():
+ # try:
+ # with open("config.json", "r") as f:
+ # config = json.load(f)
+
+ # for ap in config.get("access_points", []):
+ # result = wifi_connect(
+ # ap["ssid"], xor_encrypt_decrypt(ap["secret"], config["xor_key"])
+ # )
+ # if result:
+ # return
+
+ # except (OSError, ValueError) as e:
+ # print("OSError config.json unreadable or no")
+
+ print("Falling back on selfhosted AP")
+ ap = network.WLAN(network.AP_IF)
+ ap.config(essid="RP2040-AP", password="openwater")
+ ap.ifconfig(("192.168.4.1", "255.255.255.0", "192.168.4.1", "192.168.4.1"))
+ ap.active(True)
+ print("SSID RP2040-AP")
+ return
+
+
+print("Attempting to connect to wifi")
+# time.sleep(10)
+wifi_setup()
diff --git a/open_water_change/main.py b/open_water_change/main.py
new file mode 100644
index 0000000..ca5b551
--- /dev/null
+++ b/open_water_change/main.py
@@ -0,0 +1,67 @@
+import network
+import socket
+import machine
+
+
+# Setup RP2040 as an access point
+# ap = network.WLAN(network.AP_IF)
+# ap.config(essid="RP2040-AP", password="testing123")
+# ap.ifconfig(("192.168.4.1", "255.255.255.0", "192.168.4.1", "192.168.4.1"))
+# ap.active(True)
+
+# Initialize onboard LED
+led = machine.Pin("LED", machine.Pin.OUT, value=1)
+motor_ctl_a = machine.Pin(16, machine.Pin.OUT, value=0)
+motor_ctl_b = machine.Pin(17, machine.Pin.OUT, value=0)
+
+# Create a socket and listen on port 80
+addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
+# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s = socket.socket()
+s.bind(addr)
+# only allowed to listen to 5 connections in AP mode
+s.listen(5)
+
+# print("listening on", ap.ifconfig()[0])
+
+# HTML to send to browsers
+html = """
+
+
+
+
Toggle Motor
+
+
+
+
+"""
+
+
+def serve():
+ conn, addr = s.accept()
+ print("client connected from", addr)
+ request = conn.recv(1024)
+ request_str = str(request)
+ print("request:", request_str)
+
+ if "POST /toggle" in request_str:
+ led.toggle()
+ motor_ctl_a.toggle() # Toggle LED state
+ conn.send("HTTP/1.1 200 OK\n\nLED Toggled")
+ else:
+ conn.send("HTTP/1.1 200 OK\n")
+ conn.send("Content-Type: text/html\n")
+ conn.send("Connection: close\n\n")
+ conn.sendall(html) # Send HTML page
+ conn.close()
+
+
+# Main loop: accept connections and respond
+while True:
+ serve()
diff --git a/webserver/turtle_bot_alt.md b/webserver/turtle_bot_alt.md
new file mode 100644
index 0000000..ee810b9
--- /dev/null
+++ b/webserver/turtle_bot_alt.md
@@ -0,0 +1,8 @@
+A Turtlebot Alternative
+
+Dear Reader,
+
+Why would you pay $1,336.99 for a Turtlebot4 when you could build one for less than ? Today I’ll break down how I built a ROS2 Turtlebot4 clone, and saved hundreds of dollars in the process.
+
+What makes a Turtlebot?
+Well, a [Turtlebot](https://clearpathrobotics.com/turtlebot-4/) is actually learning platform robot built and sold by Clearpath Robotics dw
diff --git a/webserver/webserver.py b/webserver/webserver.py
index 5206ca7..8aacdfd 100644
--- a/webserver/webserver.py
+++ b/webserver/webserver.py
@@ -6,12 +6,13 @@ except ImportError:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-s.bind(('', 80))
+s.bind(("", 80))
s.listen(5)
def render_html(context: dict = {}):
- html = """
+ html = (
+ """