docs: add project description and server setup instructions to README

This commit is contained in:
2026-03-21 18:25:35 +00:00
parent c81815a83d
commit 6115a065c7
36 changed files with 4003 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
"""
CoAP Observe Manager (RFC 7641)
Manages observer registrations for CoAP resources on the ESP server side.
Each resource can have multiple observers. When the resource state changes,
all registered observers receive a notification.
Observer entry structure:
{
"ip": str, # Observer IP address
"port": int, # Observer UDP port
"token": bytearray, # Token from the original GET request (for matching)
}
"""
try:
import time
except ImportError:
import utime as time
class ObserveManager:
"""Manages CoAP Observe subscriptions per resource URI."""
# Maximum observers per resource (memory constrained on ESP)
MAX_OBSERVERS_PER_RESOURCE = 4
# Maximum total observers across all resources
MAX_TOTAL_OBSERVERS = 8
def __init__(self, debug=True):
# Dict of resource_url -> list of observer entries
self._observers = {}
# Per-resource sequence counter (24-bit, wraps at 0xFFFFFF)
self._sequence = {}
self.debug = debug
def log(self, s):
if self.debug:
print("[observe]: " + s)
def register(self, resource_url, ip, port, token):
"""Register an observer for a resource.
Returns True if successfully registered, False if limits exceeded.
"""
if resource_url not in self._observers:
self._observers[resource_url] = []
self._sequence[resource_url] = 0
observers = self._observers[resource_url]
# Check if this observer is already registered (same ip+port+token)
for obs in observers:
if obs["ip"] == ip and obs["port"] == port:
# Update the token (re-registration)
obs["token"] = token
self.log("Re-registered observer {}:{} for {}".format(ip, port, resource_url))
return True
# Check limits
total = sum(len(v) for v in self._observers.values())
if total >= self.MAX_TOTAL_OBSERVERS:
self.log("Max total observers reached, rejecting registration")
return False
if len(observers) >= self.MAX_OBSERVERS_PER_RESOURCE:
self.log("Max observers for {} reached, rejecting".format(resource_url))
return False
observers.append({
"ip": ip,
"port": port,
"token": token,
})
self.log("Registered observer {}:{} for {} (token={})".format(
ip, port, resource_url, token
))
return True
def deregister(self, resource_url, ip, port):
"""Remove an observer for a resource."""
if resource_url not in self._observers:
return
observers = self._observers[resource_url]
self._observers[resource_url] = [
obs for obs in observers
if not (obs["ip"] == ip and obs["port"] == port)
]
self.log("Deregistered observer {}:{} from {}".format(ip, port, resource_url))
def deregister_by_token(self, resource_url, token):
"""Remove an observer by token (used when RST is received)."""
if resource_url not in self._observers:
return
observers = self._observers[resource_url]
self._observers[resource_url] = [
obs for obs in observers
if obs["token"] != token
]
def deregister_all(self, resource_url):
"""Remove all observers for a resource."""
if resource_url in self._observers:
del self._observers[resource_url]
del self._sequence[resource_url]
def get_observers(self, resource_url):
"""Get the list of observers for a resource."""
return self._observers.get(resource_url, [])
def has_observers(self, resource_url):
"""Check if a resource has any registered observers."""
return len(self._observers.get(resource_url, [])) > 0
def next_sequence(self, resource_url):
"""Get and increment the sequence number for a resource (24-bit wrap)."""
if resource_url not in self._sequence:
self._sequence[resource_url] = 0
seq = self._sequence[resource_url]
self._sequence[resource_url] = (seq + 1) & 0xFFFFFF
return seq
def get_all_resources(self):
"""Get all resource URLs that have observers."""
return list(self._observers.keys())
def observer_count(self, resource_url=None):
"""Get observer count. If resource_url is None, returns total count."""
if resource_url:
return len(self._observers.get(resource_url, []))
return sum(len(v) for v in self._observers.values())
def summary(self):
"""Return a summary string of all observer registrations."""
parts = []
for url, observers in self._observers.items():
addrs = ["{}:{}".format(o["ip"], o["port"]) for o in observers]
parts.append("{} -> [{}]".format(url, ", ".join(addrs)))
return "; ".join(parts) if parts else "(no observers)"