diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..82edb9e07b9d82cf2a0da0a83fd523526685a6cb
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 David Huss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 92a373a6f0e99876b0e404b4ec5566d0071498ff..10b241883ac742ad02f0f5fd5cd0205f82f7690d 100644
--- a/README.md
+++ b/README.md
@@ -8,14 +8,42 @@ mediactl sports a touchscreen that allows control of the media technology in tha
 
 ## architecture
 
-There is a fastapi web service (`localhost:8000`) running behind an nginx reverse proxy that exposes it to the world. The service has a systemd unit file located at `/etc/systemd/system` (see section below).
+There is a fastapi web service (`localhost:8000`) running behind an nginx reverse proxy that exposes it to the mediahell network. The service has a systemd unit file located at `/etc/systemd/system/mediactl.service` (see section below).
+
 The touchpanel is started with raspi-autologin and the `.xinit` file located in the `d0` user directory (see section below). The files of the running webservice are located at `/home/d0/mediactl` (see section below for hints if you want to edit those files).
 
-## autostart on RPI
-See [this](https://blog.r0b.io/post/minimal-rpi-kiosk/) for a description of how to start chromium via `/home/d0/.xinit` – note: this file contains the screen blanking settings (`xset s 600 0` )
+```mermaid
+%%{init: {'theme':'neutral'}}%%
+graph LR;
+  xinit -.->|runs| Chrome
+  Chrome((Chrome<br>Kiosk)) <--->|TCP| Nginx
+  Chrome <--->|Websocket| Nginx
+  Remote((Remote<br>Browser)) <-..->|TCP| Nginx
+  Remote <-..->|Websocket| Nginx
+  Nginx <---->|TCP| FastAPI["FastAPI<br>(mediactl)"]
+  Nginx <---->|Websocket| FastAPI
+  FastAPI <--->|GPIO| Periphery[[Periphery]]
+  FastAPI <--->|Ethernet| Periphery[[Periphery]]
+  FastAPI <--->|USB| Periphery[[Periphery]]
+  systemd -.->|runs| FastAPI
+```
+
+The fastapi code receives commands via Websockets from a browser and then uses the connector implementations in [src/devices](src/mediactl/devices/) to send/receive commands to these devices. The results are then sent back via websocket to said browser to give a quick feedback of what is going on. The javascript handling that is in [static/modules](static/modules).
+
+The entry point handling the websockets on the client (browser) side is [ws.js](master/static/ws.js). The javascript part could be organized better and more generically, but it works and is highly customizable.
+
+On the server side (FastAPI) most of the system state (including the device connectors mentioned above) is organized in the class `System` in [src/mediactl/system.py](src/mediactl/system.py).
+
+The main entry point of the whole application [src/mediactl/main.py](src/mediactl/main.py) is relatively typical for a FastAPI application, the only noteworthy thing is that the application maintains *two* websocket connections at the same time, one for sending/receiving commands, one for scheduled sending of the current system status back to the connected browsers. I tried for half a day to do this with one websocket, but it turned out to be more trouble than it was worth.
+
+Within that `main.py` file the async function `handle_incoming_messages(data)` is responsible for handling received commands. If one wanted to add new commands, it would be here.
+
+## Autostart chromium in kiosk mode
+See [this](https://blog.r0b.io/post/minimal-rpi-kiosk/) for a description of how to start chromium via `/home/d0/.xinit` – note: this file contains the screen blanking settings (`xset s 600 0` ) which are responsible for blacking out the screen.
 
 
-## systemd service
+
+## Systemd service
 
 **Check Service Status**
 ```bash
@@ -29,7 +57,50 @@ sudo systemctl restart mediactl.service
 
 
 
-## note about apt
+# Periphery Connections
+
+The mediactl (FastAPI) application controls all kind of periphery. 
+
+## Connection table
+
+| Device              | Connection Kind                         | Purpose                                        | Kind                                                         | Implementation                                    |
+| ------------------- | --------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------- |
+| Allen&Heath AHM16   | Ethernet                                | Audio DSP and Mixer                            | [TCP Protocol](https://www.allen-heath.com/content/uploads/2023/11/AHM-TCP-Protocol-V1.4.pdf) (essentially MIDI over IP) | [ahm16.py](src/mediactl/devices/ahm16.py)         |
+| Rpi 3B "projctl"    | Ethernet/WIFI (REST via mediahell wifi) | Controlling and monitoring the projector       | see [code.hfbk.net](https://code.hfbk.net/medientechnik/ext-lib/projctl) | [projector.py](src/mediactl/devices/projector.py) |
+| Kramer VS-411X      | GPIO and GND                            | Switching HDMI-Sources, Audio Extractor        | GPIO, Pull port to GND                                       | [kramer.py](src/mediactl/devices/kramer.py)       |
+| ~~Quadro DSP~~      | ~~(USB-Serial)~~                        | ~~Poweramp, Check State~~                      | [Reverse Engineering](https://reverseengineering.stackexchange.com/questions/25066/t-amp-quadro-500-dsp-calculate-checksum) | -                                                 |
+| ~~Sennheiser ewG4~~ | ~~(Ethernet)~~                          | ~~Wireless Mic Receiver, Check State~~         | [Manual](https://www.sennheiser.com/globalassets/digizuite/41944-en-ti_1254_metromediensteuerung_ewg4_en.pdf) | -                                                 |
+| AC123 Remote        | GPIO and GND                            | Controls Screen motor via Radio (Up/Down/Stop) | GPIO, Pull port to GND to switch                             | [screen.py](src/mediactl/devices/screen.py)       |
+
+
+
+### GPIO Connections
+
+| GPIO PIN | Device       | Function          | Kind                  | Color                 |
+| -------- | ------------ | ----------------- | --------------------- | --------------------- |
+| GND (39) | Kramer       | GND               | GND                   | Black                 |
+| GPIO 21  | Kramer       | Switch to Input 4 | Pull to GND to switch | Brown+Brown/White     |
+| GPIO 20  | Kramer       | Switch to Input 3 | Pull to GND to switch | Blue + Blue/White     |
+| GPIO 26  | Kramer       | Switch to Input 2 | Pull to GND to switch | Orange + Orange/White |
+| GPIO 16  | Kramer       | Switch to Input 1 | Pull to GND to switch | Green + Green/White   |
+| GND (34) | AC123 Remote | GND               | GND                   | Black                 |
+| GPIO 13  | AC123 Remote | Screen moves Up   | Pull to GND to switch | Green                 |
+| GPIO 6   | AC123 Remote | Screen stops      | Pull to GND to switch | White                 |
+| GPIO 5   | AC123 Remote | Screen moves Down | Pull to GND to switch | Red                   |
+
+## AC123-Remote
+
+This is the screen remote we got from the company who added the motorized screen to the room. I tried to reverse engineer the custom 433 Mhz protocol, but then decided to just use the remote control which offers 3.3V pins. This has the benefit that mediactl could always "know" wheter the screen has previously been moved down or not (except if you shutdown the system inbetween).
+
+![](images/ac123-pinout.jpg)
+
+
+# Development
+
+
+## Isolation in mediahell, how to use apt
+
+Devices can't reach the outside world in mediahell (hence the name). This also includes things like `apt`. To update/install packages you have to use a SSH-tunnel to your dev machine.
 
 In `/etc/apt/apt.conf.d/proxy.conf` there is a proxy connection setup:
 
@@ -57,35 +128,3 @@ sudo sshfs -o allow_other,default_permissions -o IdentityFile=/home/YOURUSER/.ss
 ```
 
 **⚠ Important:** If you edit any of the static files (css/images/js/etc) you need to run the `./deploy_static.sh` script afterwards, else those changes will not be used in production !
-
-## Connection diagram
-
-| Device            | Connection Kind                         | Purpose                                        |                                                              |
-| ----------------- | --------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------ |
-| Allen&Heath AHM16 | Ethernet                                | Audio DSP and Mixer                            | [TCP Protocol](https://www.allen-heath.com/content/uploads/2023/11/AHM-TCP-Protocol-V1.4.pdf) (essentially MIDI over IP) |
-| RPI 3B "projctl"  | Ethernet/WIFI (REST via mediahell wifi) | Controlling and monitoring the projector       | see [code.hfbk.net](https://code.hfbk.net/medientechnik/ext-lib/projctl) |
-| Kramer VS-411X    | GPIO and GND                            | Switching HDMI-Sources, Audio Extractor        | GPIO, Pull port to GND                                       |
-| Quadro DSP        | (USB-Serial)                            | Poweramp, Check State                          | [Reverse Engineering](https://reverseengineering.stackexchange.com/questions/25066/t-amp-quadro-500-dsp-calculate-checksum) |
-| Sennheiser ewG4   | (Ethernet)                              | Wireless Mic Receiver, Check State             | [Manual](https://www.sennheiser.com/globalassets/digizuite/41944-en-ti_1254_metromediensteuerung_ewg4_en.pdf) |
-| AC123 Remote      | GPIO and GND                            | Controls Screen motor via Radio (Up/Down/Stop) | GPIO, Pull port to GND to switch                             |
-
-
-
-### GPIO Connections
-
-| GPIO PIN | Device       | Function          | Kind                  | Color                 |
-| -------- | ------------ | ----------------- | --------------------- | --------------------- |
-| GND (39) | Kramer       | GND               | GND                   | Black                 |
-| GPIO 21  | Kramer       | Switch to Input 4 | Pull to GND to switch | Brown+Brown/White     |
-| GPIO 20  | Kramer       | Switch to Input 3 | Pull to GND to switch | Blue + Blue/White     |
-| GPIO 26  | Kramer       | Switch to Input 2 | Pull to GND to switch | Orange + Orange/White |
-| GPIO 16  | Kramer       | Switch to Input 1 | Pull to GND to switch | Green + Green/White   |
-| GND (34) | AC123 Remote | GND               | GND                   | Black                 |
-| GPIO 13  | AC123 Remote | Screen moves Up   | Pull to GND to switch | Green                 |
-| GPIO 6   | AC123 Remote | Screen stops      | Pull to GND to switch | White                 |
-| GPIO 5   | AC123 Remote | Screen moves Down | Pull to GND to switch | Red                   |
-
-## AC123-Remote
-
-![](images/ac123-pinout.jpg)
-
diff --git a/pyproject.toml b/pyproject.toml
index 6dfcfa777c6f606f9a80917d7f5907a786d1bfed..267efa741b7ad4f4bd7c4ba31438a648e5bae908 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "mediactl"
-version = "0.1.0"
+version = "0.1.1"
 description = "Add your description here"
 dependencies = [
     "fastapi[standard]>=0.114.2",
diff --git a/src/mediactl/devices/ahm16.py b/src/mediactl/devices/ahm16.py
index d55aa618616a86c25866f486c18878d2907bb565..14f03cae540424cae4f657c0ac620377ae5a6096 100644
--- a/src/mediactl/devices/ahm16.py
+++ b/src/mediactl/devices/ahm16.py
@@ -1,5 +1,6 @@
 import asyncio
 import aioping
+import errno
 from datetime import datetime
 from typing import Union, List
 from result import Ok, Err, Result, is_ok, is_err  # noqa: F401
@@ -96,6 +97,7 @@ class PowerState(mediactl.EnumState):
     on = "on"
     off = "off"
     unknown = "unknown"
+    disconnected = "disconnected"
 
     def is_on(self) -> bool:
         return self in [PowerState.on]
@@ -103,6 +105,9 @@ class PowerState(mediactl.EnumState):
     def is_off(self) -> bool:
         return not self.is_on()
 
+    def is_disconnected(self) -> bool:
+        return self in [PowerState.disconnected]
+
 
 class Channel(mediactl.WithLogger):
     def __init__(self, number: int, logger=None):
@@ -233,6 +238,17 @@ class Ahm16(mediactl.WithLogger):
                 else str(self.status["last-seen"])
             )
             self.log_error(f"AHM could not_be_reached. Last seen: {last}")
+        except OSError as e:
+            if e.errno == errno.ENETUNREACH:
+               self.status["power"] = PowerState.disconnected 
+               last = "never" if self.status["last-seen"] is None else str(self.status["last-seen"])
+                self.log_error(f"AHM Dante Network is down or AHM unreachable {e.errno}: {e.strerror}. Last seen: {last}")
+            else:
+                # Othernetwork‐level failure (no route, interface down, etc.)
+                self.status["power"] = PowerState.unknown
+                last = "never" if self.status["last-seen"] is None else str(self.status["last-seen"])
+                self.log_error(f"AHM ping OSError {e.errno}: {e.strerror}. Last seen: {last}")
+
 
     async def get_status(self, name=False):
         await self.ping()
diff --git a/static/style.css b/static/style.css
index d1968331726de355c9bfa64e16279d78cdaeb500..a733bc40f7a277e4e31f69c799538cc75bb57890 100644
--- a/static/style.css
+++ b/static/style.css
@@ -34,6 +34,15 @@ main {
 	display: none;
 }
 
+@keyframes blink-red {
+  0%, 45% {
+    background-color: var(--color-red);
+  }
+  60%, 100% {
+    background-color: transparent;
+  }
+}
+
 header {
 	width: 100%;
 	border-bottom: 1px solid white;
@@ -90,6 +99,10 @@ header {
 	.warmup .status_text { color: var(--color-orange); }
 	.off .led { background-color: var(--color-red); }
 	.off .status_text { color: var(--color-red); }
+	.disconnected .led {
+	  background-color: transparent;
+	  animation: blink-red 1s infinite;
+	}
 }
 
 main.disconnected{