Skip to content

Commit

Permalink
Initial draft
Browse files Browse the repository at this point in the history
  • Loading branch information
fritzm committed Jul 30, 2024
1 parent b9a5d50 commit 46db7fb
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 7 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

## TSTN-047

A solution is proposed to provide web dashboard control of the infrared illuminators on the UniFi web cameras deployed throughout the observatory, with needing to provide and admin-level UniFi account for each operator.
Proposed solution to provide web dashboard control of the infrared illuminators on the UniFi web cameras
deployed throughout the observatory, without needing to provide and admin-level UniFi account for each
operator.

**Links:**

Expand Down
74 changes: 71 additions & 3 deletions index.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,77 @@
# Operator Control of Rubin Observatory NVR Camera Infrared Illuminators

```{abstract}
A solution is proposed to provide web dashboard control of the infrared illuminators on the UniFi web cameras deployed throughout the observatory, without needing to provide and admin-level UniFi account for each operator.
Proposed solution to provide web dashboard control of the infrared illuminators on the UniFi web cameras
deployed throughout the observatory, without needing to provide and admin-level UniFi account for each
operator.
```

## Add content here
## Introduction

See the [Documenteer documentation](https://documenteer.lsst.io/technotes/index.html) for tips on how to write and configure your new technote.
Operators at the observatory occasionally need to control the illuminators on the UniFi web cameras deployed
throughout the observatory. This has been problematic, since such control via the stock UniFi web interface
requires a UniFi account with administrator-level privileges and the system supports only a limited number of
such accounts. Broad distribution of a shared admin-level account with full control over the UniFi system to
the entire operator staff has been viewed as a non-starter from a security perspective.

A survey of available Python libraries which might be used to control the illuminators via UniFi-provided web
APIs was conducted. Unfortunately, a compelling option was not immediately apparent; the UniFi APIs
themselves are rather poorly documented, and the off-the-shelf client libraries currently available are the
usual assortment of abandonware, partially implemented, or outdated solutions.

In the course of the survey, however, it was discovered that there is a fairly complete and up-to-date support
for UniFi NVR camera systems within the [Home Assistant](https://home-assistant.io) home automation platform.
Home Assistant is a popular and vigorous open-source project with hundreds-of-thousands of installations
world-wide. In addition to providing an up to date, maintained, and complete interface to the UniFi camera
systems, the Home Assistant platform itself provides a tailorable web control UI, configuartion support,
authentication plugins, etc. all directly out of the box. Home Assistant is also distributed as a pre-built
container that can be directly utilized on the observator Kubernetes clusters. It was decided to evaluate the
use of Home Asisstant and its [in-built UniFi
support](https://www.home-assistant.io/integrations/unifiprotect/) as a potential low-effort near-term
solution.

## Prototype

IT provided the following for the purposes of prototyping:

* Access to the kueyen dev cluster
* A DNS CNAME (`test-nvr.dev.lsst.org`) for the ingres controller
* A dedicated, local service account on the NVR appliance

A simple Kubernetes deployment using the latest official Home Assistant release container was hand-coded and
manually deployed with `kubectl`:

```{figure} kubernetes.svg
Prototype Kubernetes Deployment
```

Home Assistant was then configured via its built-in UI. Once pointed at the NVR appliance and auth'd, all
on-site UniFi cameras were automatically introspected and made available in the interface.

A dedicated "kiosk mode" page which exposes illuminator controls only was built, using a kiosk plug-in
available in the Home Assistant community store. This was set as the default view for a shared "operators"
local Home Assistant account, and we had a fully operable system up and running:

```{figure} interface.png
Prototype illuminator control page
```

## To Do

Use of Home Assistant for this purpose in the near term seems completely feasible from a technical
perspective. Should we wish to do this, the following would still need to be done to close the gap between a
prototype and an operable deployment:

* If desired, integrate with Keycloak authentication for finer-grained access control. This would obviate
need for a shared Home Assistant "operator" account and password. This is feasible, but perhaps not an
issue since the dashboard is limited to illuminators only? <br><br>

* Decide which organization within the observatory would adopt and maintain the deployment. This would
seemingly be either Telescope and Site, or IT. The deployment described here has no direct integration with
the observatory control systems, and interacts only with IT owned/administrated systems. <br><br>


* "Devops-ify" the deployment. This would mean wrapping up the existing prototype deployment
yamls in either Rancher Fleet (if adopted by IT) or Phalanx (if adopted by Telescope and Site). <br><br>

* Arrange for backup of the persistent volume on which the Home Assistant configuration is stored. <br><br>
Binary file added interface.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions kubernetes.drawio
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.7.5 Chrome/126.0.6478.183 Electron/31.3.0 Safari/537.36" version="24.7.5">
<diagram name="Page-1" id="1ndsgg4-D2hNgmVH_vr4">
<mxGraphModel dx="1036" dy="702" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" background="none" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="aDZf9LyQNGa_F-r6yj7t-6" value="" style="whiteSpace=wrap;html=1;fillColor=none;dashed=1;rounded=1;arcSize=9;" parent="1" vertex="1">
<mxGeometry x="100" y="130" width="480" height="360" as="geometry" />
</mxCell>
<mxCell id="A0QEv23gE5V9U2TAgRKM-1" value="" style="aspect=fixed;sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=bottom;verticalAlign=top;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;kubernetesLabel=1;prIcon=deploy" parent="1" vertex="1">
<mxGeometry x="170" y="290" width="50" height="48" as="geometry" />
</mxCell>
<mxCell id="A0QEv23gE5V9U2TAgRKM-2" value="x1" style="aspect=fixed;sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=bottom;verticalAlign=top;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;kubernetesLabel=1;prIcon=rs" parent="1" vertex="1">
<mxGeometry x="275" y="290" width="50" height="48" as="geometry" />
</mxCell>
<mxCell id="A0QEv23gE5V9U2TAgRKM-3" value="Home&lt;br&gt;Assistant" style="aspect=fixed;sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=middle;verticalAlign=middle;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;kubernetesLabel=1;prIcon=pod;labelPosition=right;align=center;spacingLeft=6;" parent="1" vertex="1">
<mxGeometry x="380" y="290" width="50" height="48" as="geometry" />
</mxCell>
<mxCell id="A0QEv23gE5V9U2TAgRKM-5" value="" style="aspect=fixed;sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=bottom;verticalAlign=top;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;kubernetesLabel=1;prIcon=pv" parent="1" vertex="1">
<mxGeometry x="485" y="390" width="50" height="48" as="geometry" />
</mxCell>
<mxCell id="A0QEv23gE5V9U2TAgRKM-6" value="/config" style="aspect=fixed;sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=bottom;verticalAlign=top;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;kubernetesLabel=1;prIcon=pvc;labelPosition=center;align=center;" parent="1" vertex="1">
<mxGeometry x="380" y="390" width="50" height="48" as="geometry" />
</mxCell>
<mxCell id="A0QEv23gE5V9U2TAgRKM-7" value="" style="aspect=fixed;sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=bottom;verticalAlign=top;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;kubernetesLabel=1;prIcon=svc" parent="1" vertex="1">
<mxGeometry x="275" y="190" width="50" height="48" as="geometry" />
</mxCell>
<mxCell id="A0QEv23gE5V9U2TAgRKM-8" value="test-nvr.dev.lsst.org&lt;br&gt;(ssl termination)" style="aspect=fixed;sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=bottom;verticalAlign=top;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;kubernetesLabel=1;prIcon=ing;labelPosition=center;align=center;" parent="1" vertex="1">
<mxGeometry x="170" y="190" width="50" height="48" as="geometry" />
</mxCell>
<mxCell id="A0QEv23gE5V9U2TAgRKM-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.995;exitY=0.63;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.005;entryY=0.63;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="A0QEv23gE5V9U2TAgRKM-1" target="A0QEv23gE5V9U2TAgRKM-2" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="A0QEv23gE5V9U2TAgRKM-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.995;exitY=0.63;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.005;entryY=0.63;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="A0QEv23gE5V9U2TAgRKM-2" target="A0QEv23gE5V9U2TAgRKM-3" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="A0QEv23gE5V9U2TAgRKM-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="A0QEv23gE5V9U2TAgRKM-3" target="A0QEv23gE5V9U2TAgRKM-6" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="A0QEv23gE5V9U2TAgRKM-13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.995;exitY=0.63;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.005;entryY=0.63;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="A0QEv23gE5V9U2TAgRKM-6" target="A0QEv23gE5V9U2TAgRKM-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="aDZf9LyQNGa_F-r6yj7t-1" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.995;exitY=0.63;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.1;entryY=0.2;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="A0QEv23gE5V9U2TAgRKM-7" target="A0QEv23gE5V9U2TAgRKM-3" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="aDZf9LyQNGa_F-r6yj7t-3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.995;exitY=0.63;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.005;entryY=0.63;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="A0QEv23gE5V9U2TAgRKM-8" target="A0QEv23gE5V9U2TAgRKM-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="A0QEv23gE5V9U2TAgRKM-4" value="home-assistant" style="aspect=fixed;sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=bottom;verticalAlign=top;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;kubernetesLabel=1;prIcon=ns" parent="1" vertex="1">
<mxGeometry x="140" y="100" width="50" height="48" as="geometry" />
</mxCell>
<mxCell id="aDZf9LyQNGa_F-r6yj7t-8" value="to on-prem&lt;br&gt;NVR appliance&lt;br&gt;using local service account" style="html=1;outlineConnect=0;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.comm_link_edge;html=1;rounded=0;labelBackgroundColor=none;" parent="1" edge="1">
<mxGeometry x="1" y="-38" width="100" height="100" relative="1" as="geometry">
<mxPoint x="425" y="288" as="sourcePoint" />
<mxPoint x="465" y="238" as="targetPoint" />
<mxPoint x="-5" y="-52" as="offset" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
4 changes: 4 additions & 0 deletions kubernetes.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions technote.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ organization.name = "Vera C. Rubin Observatory"
organization.ror = "https://ror.org/048g3cy84"
license.id = "CC-BY-4.0"

[technote.status]
state = "draft"

[[technote.authors]]
name.given = "Fritz"
name.family = "Mueller"
Expand Down

0 comments on commit 46db7fb

Please sign in to comment.