A local NOC dashboard for checking and controlling a driveway gate via a Remootio smart gate controller connected to a Ghost Controls gate system.
The repo ships two equivalent front-end implementations and one shared .NET library:
| Project | Stack | Description |
|---|---|---|
angular/ |
Angular 22 + RxJS | Original dashboard — runs in the browser, connects to the Remootio device WebSocket directly |
dotnet/GateMonitor.Blazor |
Blazor WebAssembly (.NET 10) | Equivalent SPA — same direct WebSocket approach, orchestrated by Aspire |
dotnet/myNOC.Remootio |
.NET 8 / 10 class library | Remootio protocol implementation — also published as a NuGet package |
gateMonitor/
├── angular/ # Angular dashboard app + remootio-angular NPM library
│ ├── src/ # App UI (home component, routing)
│ └── projects/remootio-angular/ # Angular library (Remootio WebSocket client)
├── dotnet/
│ ├── GateMonitor.AppHost/ # .NET Aspire orchestrator (dev launcher)
│ ├── GateMonitor.Blazor/ # Blazor WASM dashboard
│ ├── myNOC.Remootio/ # Remootio library → NuGet: myNOC.Remootio
│ ├── tests/
│ │ ├── myNOC.Tests.Remootio/ # MSTest unit tests for the library
│ │ └── GateMonitor.Tests.Blazor/# bUnit component tests for the Blazor app
│ └── GateMonitor.slnx # .NET solution (SLNX format)
└── .github/
├── workflows/
│ ├── dotnet-build.yml # PR: build + test (.NET & Angular)
│ └── dotnet-release.yml # Main: NuGet + NPM publish + tag
└── instructions/ # Copilot architecture guidance
The Angular app connects directly from the browser to the Remootio device WebSocket (ws://{deviceIp}:8080/), authenticates, and subscribes to gate state events.
cd angular
npm install
npm install --prefix ./projects/remootio-angular/
ng build remootio-angular
ng serve -oEdit angular/src/app/pages/home/home.component.ts and replace the placeholders:
this.remootioService.connect({
deviceIp: '{remootioDeviceIp}',
apiSecretKey: '{apiSecretKey}', // 64-char hex — from Remootio app
apiAuthKey: '{apiAuthKey}', // 64-char hex — from Remootio app
autoReconnect: true
});The gate image URL placeholder is also in that file. Keys are found in the Remootio mobile app under Settings → WebSocket API.
The Blazor app is a standalone WebAssembly SPA that also connects directly from the browser to the Remootio device. It uses the myNOC.Remootio library.
cd dotnet
dotnet run --project GateMonitor.AppHostThis launches the Aspire dashboard and starts the Blazor dev server. Open the Aspire dashboard URL to navigate to the app.
cd dotnet
dotnet run --project GateMonitor.Blazor
# opens http://localhost:5122Edit dotnet/GateMonitor.Blazor/wwwroot/appsettings.json:
{
"Remootio": {
"DeviceIp": "192.168.1.50",
"ApiSecretKey": "<64-char hex>",
"ApiAuthKey": "<64-char hex>",
"AutoReconnect": true,
"GateImageUrl": "http://192.168.1.51/snapshot.jpg"
}
}Note:
wwwroot/appsettings.jsonis served as a public static file. Do not commit real credentials — useappsettings.Development.json(git-ignored) or environment-local overrides.
A .NET port of the remootio-api-client-node library. Handles AES-CBC + HMAC-SHA256 encryption, the authentication challenge/response flow, and real-time state change events.
See dotnet/myNOC.Remootio/README.md for full API documentation.
Install:
dotnet add package myNOC.Remootio| Workflow | Trigger | What it does |
|---|---|---|
dotnet-build.yml |
PR → main | GitVersion, .NET build + test, Angular build |
dotnet-release.yml |
Push → main | GitVersion, NuGet push, NPM publish, git tag + GitHub release |
Required secrets: NUGET_PUBLISH, npm_token
Once the connection to Remootio is made and authenticated the web site will display a bar indicating if the gate is open or closed and 2 buttons. One to Open and one to Close the gate.
<mat-card *ngIf="isAuthenticated">
<mat-card-title>Status</mat-card-title>
<mat-card-content class="statusCards">
<div class="gateStatus"
[ngClass]="{'opened': (gateState$ | async)?.isOpen, 'closed': !(gateState$ | async)?.isOpen}">
{{ (gateState$ | async)?.description }}</div>
<button (click)='closeGate()' mat-fab class="closed" [disabled]="!(gateState$ | async)?.isOpen">Close</button>
<button (click)='openGate()' mat-fab class="opened" [disabled]="(gateState$ | async)?.isOpen">Open</button>
</mat-card-content>
</mat-card>The style sheet can be found in styles.scss. The bar is Green when the gate is closed or Red when open.
To display your own gate image, you will need to change the gateImage URL. To get the proper URL for a snap image from your camera you will need to look at your camera documentation. I use a UniFi G4 Instance, so once enabled the URL is just https://{cameraIp}/snap.jpeg. Your camera will most likely be different.
private gateImage: string = "{cameraSnapUrl}";This URL will get called every 1 second so it is updated in the UI. The Date.now() is added so the image isn't cached by the browser.
setInterval(() => this.gateImage$.next(`${this.gateImage}?${Date.now()}`), 1000);HTML to display the gate image.
<img src="{{ gateImage$ | async }}" alt="Gate" class="gateImage" />To run the site you will need Angular 22 CLI
npm install -g @angular/cliOnce that is installed you should run npm install in the angular folder.
cd angular
npm installOnce you have ran npm install, build the remootio-angular library:
cd angular
ng build remootio-angularThen build and run the main Angular project:
ng serve -o