Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new unifi-protect node #48

Merged
merged 43 commits into from
Nov 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
8cb6b9a
Attempt to obtain bootstrap after login (and at every refresh)
marcus-j-davies Oct 10, 2022
c846ba1
Bootstrap is GET not POST
marcus-j-davies Oct 10, 2022
c153d1c
Add ability to notify our Nodes of newly retrieved Bootstrap
marcus-j-davies Oct 10, 2022
accbfd6
Expose an Admin HTTP endpoint so node config panels can get the cams …
marcus-j-davies Oct 10, 2022
6b6b57a
Start of protect node (we can send updates)
marcus-j-davies Oct 10, 2022
075487c
Incorporate lastUpdateId updates
marcus-j-davies Oct 10, 2022
b3b66df
Not nobody wants 16+ websockets - lets use a shared approach (more to…
marcus-j-davies Oct 11, 2022
b3ba70b
Initial event stream + sending commands (Still WIP)
marcus-j-davies Oct 11, 2022
6d4081b
Implement `updateLastUpdateId` and recovery logic
marcus-j-davies Oct 11, 2022
233eeec
Ensure Admin HTTP endpoint is removed from the stack
marcus-j-davies Oct 11, 2022
60a8a50
Event Payload : First Draft
marcus-j-davies Oct 13, 2022
3303468
Typo
marcus-j-davies Oct 13, 2022
05d3b15
Typo
marcus-j-davies Oct 13, 2022
deb5d05
Debug
marcus-j-davies Oct 13, 2022
6426d04
Name update
marcus-j-davies Oct 13, 2022
bcadc4b
Remove Debug
marcus-j-davies Oct 13, 2022
0d6931e
Cleanup a little mess
marcus-j-davies Oct 13, 2022
433a0f0
Switch to lodash + Fix Config UI + Tidy up Payload + create shapeProf…
marcus-j-davies Oct 14, 2022
3acbb5f
Fix WS Camera logic for updates
marcus-j-davies Oct 14, 2022
f30ab55
Move to new start location
marcus-j-davies Oct 14, 2022
1267a78
Identify Camera in payload
marcus-j-davies Oct 14, 2022
b9ea2b9
Adjust camera object
marcus-j-davies Oct 14, 2022
2eee73d
Add Motion Detection model
marcus-j-davies Oct 15, 2022
ee9bcd9
Improve end event creation
marcus-j-davies Oct 15, 2022
64dd2a7
Snapshot + Payload improvements + remove inline axios request cfg
marcus-j-davies Oct 23, 2022
c8584b8
Resolve conflicts #1
marcus-j-davies Oct 23, 2022
d1e74bf
Merge branch 'main' into protect-node
marcus-j-davies Oct 23, 2022
e37ba35
Resolve conflicts
marcus-j-davies Oct 23, 2022
ed64257
Update package.json
marcus-j-davies Oct 23, 2022
ab34768
Update package-lock.json
marcus-j-davies Oct 23, 2022
32c0bb6
Update package.json
marcus-j-davies Oct 23, 2022
2ab14ab
Update EventModels.ts
marcus-j-davies Oct 23, 2022
ef8ab40
Fix up snapshot mechanics
marcus-j-davies Oct 24, 2022
4ee679a
This it?
marcus-j-davies Oct 24, 2022
5018796
Slight overlook - clear out last selected cam
marcus-j-davies Oct 24, 2022
e290bf2
Address some review comments
marcus-j-davies Oct 26, 2022
e2ee5d3
Address more comments
marcus-j-davies Oct 26, 2022
d45cb70
move away from `any`
marcus-j-davies Oct 27, 2022
287f2fc
Align with formatting + use switch statement for better readability
marcus-j-davies Oct 27, 2022
3bf7a97
Add network recovery
marcus-j-davies Nov 17, 2022
6a8989d
Address bulk of changes
marcus-j-davies Nov 18, 2022
1fdd28c
HTML hide and uneeded await
marcus-j-davies Nov 20, 2022
593d81e
Small correction to `snapshotAvailability` for end events
marcus-j-davies Nov 20, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 95 additions & 42 deletions build/nodes/AccessController.html
Original file line number Diff line number Diff line change
@@ -1,72 +1,125 @@
<script type="text/javascript">
RED.nodes.registerType('unifi-access-controller',
{
category: 'config',
defaults:
{
name: {
value: '',
required: true
},
controllerIp: {
value: '',
required: true
},
controllerPort: {
value: '',
required: false
},
controllerType: {
value: 'UniFiOSConsole',
required: true
}
},
credentials: {
username: {type: "text"},
password: {type: "password"}
RED.nodes.registerType('unifi-access-controller', {
category: 'config',
defaults: {
name: {
value: '',
required: true,
},
label: function () {
return this.name || "unifi-access-controller";
controllerIp: {
value: '',
required: true,
},
labelStyle: function () {
return this.name ? 'node_label_italic' : ''
}
});
controllerPort: {
value: '',
required: false,
},
controllerType: {
value: 'UniFiOSConsole',
required: true,
},
protectSocketReconnectTimeout: {
value: 0,
validate: RED.validators.number(),
required: true,
},
protectSocketHeartbeatInterval: {
value: 0,
validate: RED.validators.number(),
required: true,
},
},
oneditprepare: restoreValue,
oneditsave: storeAsNumber,
credentials: {
username: { type: 'text' },
password: { type: 'password' },
},
label: function () {
return this.name || 'unifi-access-controller'
},
labelStyle: function () {
return this.name ? 'node_label_italic' : ''
},
})

function storeAsNumber() {
const el_protectSocketReconnectTimeout = $(
'#el-node-config-input-protectSocketReconnectTimeout'
)
this.protectSocketReconnectTimeout = parseInt(
el_protectSocketReconnectTimeout.val()
)

const el_protectSocketHeartbeatInterval = $(
'#el-node-config-input-protectSocketHeartbeatInterval'
)
this.protectSocketHeartbeatInterval = parseInt(
el_protectSocketHeartbeatInterval.val()
)
}
function restoreValue() {
const reconnectValue = this.protectSocketReconnectTimeout || 0
const heartBeatValue = this.protectSocketHeartbeatInterval || 0
$('#el-node-config-input-protectSocketReconnectTimeout').val(
reconnectValue
)
$('#el-node-config-input-protectSocketHeartbeatInterval').val(
heartBeatValue
)
}
</script>

<!-- prettier-ignore -->
<script data-template-name="unifi-access-controller" type="text/html">
<div class="form-row unifi-flex">
<label for="node-config-input-name" class="unifi-width-50"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-config-input-name" class="unifi-grow" placeholder="Name">
<input type="text" id="node-config-input-name" class="unifi-grow" placeholder="Name"/>
</div>
<div class="unifi-text-divider">Controller</div>
<div class="form-row unifi-flex">
<label for="node-config-input-controllerIp" class="unifi-width-50"><i class="fa fa-globe"></i> IP</label>
<input type="text" id="node-config-input-controllerIp" class="unifi-grow" placeholder="Controller IP">
<input type="text" id="node-config-input-controllerIp" class="unifi-grow" placeholder="Controller IP"/>
</div>
<div class="form-row unifi-flex">
<label for="node-config-input-controllerPort" class="unifi-width-50"><i class="fa fa-globe"></i> Port</label>
<input type="text" id="node-config-input-controllerPort" class="unifi-grow" placeholder="8443">
<input type="text" id="node-config-input-controllerPort" class="unifi-grow" placeholder="8443"/>
</div>
<div class="form-row unifi-flex">
<label for="node-config-input-controllerType" class="unifi-width-50"><i class="fa fa-server"></i> Type</label>
<select id="node-config-input-controllerType" class="unifi-grow" >
<option value="UniFiOSConsole" selected="selected">UniFi OS Console</option>
<option value="UniFiNetworkApplication">UniFi Network Application</option>
<select id="node-config-input-controllerType" class="unifi-grow">
<option value="UniFiOSConsole" selected="selected">
UniFi OS Console
</option>
<option value="UniFiNetworkApplication">
UniFi Network Application
</option>
</select>
</div>
<div class="unifi-text-divider">Protect Shared Socket timeouts (ms, 0 - Use defaults)</div>
<div class="form-row unifi-flex">
<label for="el-node-config-input-protectSocketReconnectTimeout" class="unifi-width-50"><i class="fa fa-clock-o"></i> Reconnect</label>
<input type="number" id="el-node-config-input-protectSocketReconnectTimeout" class="unifi-grow" placeholder="90000"/>
</div>
<div class="form-row unifi-flex">
<label for="el-node-config-input-protectSocketHeartbeatInterval" class="unifi-width-50"><i class="fa fa-clock-o"></i> Heartbeat</label>
<input type="number" id="el-node-config-input-protectSocketHeartbeatInterval" class="unifi-grow" placeholder="15000"/>
</div>
<div class="unifi-text-divider">Credentials</div>
<div class="form-row unifi-flex">
<label for="node-config-input-username" class="unifi-width-50"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-config-input-username" class="unifi-grow" placeholder="Username">
<input type="text" id="node-config-input-username" class="unifi-grow" placeholder="Username"/>
</div>
<div class="form-row unifi-flex">
<label for="node-config-input-password" class="unifi-width-50"><i class="fa fa-key"></i> Password</label>
<input type="password" id="node-config-input-password" class="unifi-grow" placeholder="Password">
<label for="node-config-input-password" class="unifi-width-50" ><i class="fa fa-key"></i> Password</label>
<input type="password" id="node-config-input-password" class="unifi-grow" placeholder="Password"/>
</div>
</script>

<!-- prettier-ignore -->
<script data-help-name="unifi-access-controller" type="text/html">
<p>A simple node that logs into Unifi OS and sends the cookies to a single output.</p>
<p>
A simple node that logs into Unifi OS and sends the cookies to a single
output.
</p>
</script>

187 changes: 187 additions & 0 deletions build/nodes/Protect.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<script type="text/javascript">
let selectedCamera
let selectedController
RED.nodes.registerType('unifi-protect', {
category: 'UniFi',
defaults: {
name: {
value: '',
required: false,
},
accessControllerNodeId: {
value: '',
type: 'unifi-access-controller',
required: true,
},
cameraId: {
value: '',
required: true,
},
eventIds: {
value: [],
required: true,
},
snapshotMode: {
value: 'None',
required: true,
},
snapshotW: {
validate: RED.validators.number(),
value: 360,
marcus-j-davies marked this conversation as resolved.
Show resolved Hide resolved
required: true,
},
snapshotH: {
validate: RED.validators.number(),
value: 360,
marcus-j-davies marked this conversation as resolved.
Show resolved Hide resolved
required: true,
},
delayedSnapshotTime: {
validate: RED.validators.number(),
value: 2000,
required: true,
},
},
inputs: 1,
outputs: 2,
outputLabels: function (index) {
switch (index) {
case 0:
return 'Event Data'

case 1:
return 'Delayed Snapshots'

default:
return ''
}
},
icon: 'protect.png',
color: '#159eda',
label: function () {
return this.name || 'Unifi Protect Camera'
},
labelStyle: function () {
return this.name ? 'node_label_italic' : ''
},
oneditsave: function () {
const Element = $('#el-node-input-delayedSnapshotTime')
this.delayedSnapshotTime = parseInt(Element.val())
},
oneditprepare: function () {
selectedCamera = undefined
$('#el-node-input-delayedSnapshotTime').val(
this.delayedSnapshotTime
)
if (this.cameraId) {
selectedCamera = this.cameraId
}
$('#node-input-accessControllerNodeId').change(controllerSelected)
showHideSnapshotOptions()
},
})

function controllerSelected() {
const ControllerID = $('#node-input-accessControllerNodeId').val()
if (ControllerID && ControllerID !== '_ADD_') {
listCameras(ControllerID)
}
}

function listCameras(ControllerID) {
$('#node-input-cameraId').empty()
$('#node-input-cameraId').append(
`<option value="" selected>Choose...</option>`
)

$.getJSON(`nrchkb/unifi/bootsrap/${ControllerID}`, (data) => {
data.cameras.forEach((Camera) => {
$('#node-input-cameraId').append(
`<option value="${Camera.id}">${Camera.name} (${Camera.type})</option>`
)
})
if (selectedCamera) {
$('#node-input-cameraId').val(selectedCamera)
}
$('#node-warning').fadeOut('fast')
}).fail(function () {
$('#node-warning').fadeIn('fast')
})
}

function showHideSnapshotOptions() {
const Value = $('#node-input-snapshotMode').val()
switch (Value) {
case 'None':
$('.unifi-snapshot').fadeOut('fast')
break

default:
$('.unifi-snapshot').fadeIn('fast')
break
}
}
</script>

<!-- prettier-ignore -->
<script data-template-name="unifi-protect" type="text/html">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name" />
</div>
<div class="form-row">
<label for="node-input-accessControllerNodeId"><i class="fa fa-globe"></i> Controller</label>
<select id="node-input-accessControllerNodeId">
<option value="">Choose...</option>
</select>
</div>
<div class="form-row">
<label for="node-input-cameraId"><i class="fa fa-video-camera"></i> Camera</label >
<select id="node-input-cameraId">
<option value="" selected>Choose...</option>
</select>
</div>
<div class="form-row">
<label for="node-input-eventIds"><i class="fa fa-list"></i> Events</label >
<select id="node-input-eventIds" multiple style="height:120px">
<option value="MotionEvent">Motion Event</option>
<option value="MotionDetection">Motion Detection</option>
<option value="DoorBell">Door Bell Ring</option>
<option value="Package">Package Detected</option>
<option value="Vehicle">Vehicle Detected</option>
<option value="Person">Person Detected</option>
</select>
</div>
<div class="form-row">
marcus-j-davies marked this conversation as resolved.
Show resolved Hide resolved
<label for="node-input-snapshotMode"><i class="fa fa-image"></i> Snapshots</label>
<select id="node-input-snapshotMode" onchange="showHideSnapshotOptions()">
<option value="None" selected>None</option>
<option value="Initial">Initial Only (if supported)</option>
<option value="InitialDelayed">Initial And Delayed (if supported)</option>
<option value="InitialRetain">Retain Initial (if supported)</option>
</select>
</div>
<div class="form-row unifi-snapshot">
<label for="node-input-snapshotW"><i class="fa fa fa-arrows-h"></i> Width</label>
<input type="number" id="node-input-snapshotW" placeholder="360" />
</div>
<div class="form-row unifi-snapshot">
<label for="node-input-snapshotH"><i class="fa fa-arrows-v"></i> Height</label>
<input type="number" id="node-input-snapshotH" placeholder="360" />
</div>
<div class="form-row unifi-snapshot">
<label for="el-node-input-delayedSnapshotTime"><i class="fa fa-clock-o"></i> Delay Time</label>
<input type="number" id="el-node-input-delayedSnapshotTime" placeholder="2000" />
</div>
<div class="form-tips unifi-snapshot" id="node-tip">
<span style="font-weight:bold;">Caution:</span> The larger the snapshot size, the more time needed to deliver the event to your flow. Not all event types support a snapshot, therefore will not be included.
The <code>Delay Time</code> value represents how long to give the controller to generate any snapshot that are not immediately available at the time of the event.
</div>
<div class="form-tips" id="node-warning">
<span style="color:red; font-weight:bold;">Warning:</span> The selected console does not appear to have a protect Instance running, or it's config has just been created and needs deploying before cameras can be fetched.
</div>
</script>

<!-- prettier-ignore -->
<script data-help-name="unifi-protect" type="text/html">
<p>A Unifi Protect Camera.</p>
</script>
Binary file added build/nodes/icons/protect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion build/nodes/unifi.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@
.unifi-grow {
flex-grow: 1;
}
</style>
</style>
Loading