mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-06-04 20:05:02 -04:00
feat: add Indicator LED control capability with session keepalive refs #4875
Add a CanIndicatorLight capability and status-aware Indicator toggle button. The indicator LED on the ASH21-B, ADC2W and ASH42-B is controlled via the LightGlobal config (configManager get/setConfig); add indicatorLightOn/Off/Status to Dahua_RPC and a model-specific 'Amcrest ASH42-B RPC' Controls row, with the capability also enabled on the ASH21-B/ADC2W/generic rows. Migration zm_update-1.39.13.sql adds the column. Add Dahua_RPC keepAlive (global.keepAlive) wired into a 30s zmcontrol idle tick, plus session-expiry re-login retry in set_config and the status queries, so the long-running control daemon does not silently fail after the ~60s session timeout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -132,6 +132,7 @@ CREATE TABLE `Controls` (
|
||||
`MinWhiteSpeed` int(10) unsigned default NULL,
|
||||
`MaxWhiteSpeed` int(10) unsigned default NULL,
|
||||
`CanLight` tinyint(3) unsigned NOT NULL default '0',
|
||||
`CanIndicatorLight` tinyint(3) unsigned NOT NULL default '0',
|
||||
`HasPresets` tinyint(3) unsigned NOT NULL default '0',
|
||||
`NumPresets` tinyint(3) unsigned NOT NULL default '0',
|
||||
`HasHomePreset` tinyint(3) unsigned NOT NULL default '0',
|
||||
@@ -1212,9 +1213,10 @@ INSERT INTO `Controls` VALUES (NULL,'PSIA','Remote','PSIA',0,0,0,0,1,0,0,1,0,0,0
|
||||
INSERT INTO `Controls` VALUES (NULL,'Dahua','Ffmpeg','Dahua',0,0,1,1,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,1,1,1,1,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
|
||||
INSERT INTO `Controls` VALUES (NULL,'FOSCAMR2C','Libvlc','FOSCAMR2C',1,1,1,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,12,0,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,4,0,NULL,1,NULL,NULL,NULL,NULL,1,0,4,0,NULL,0,0);
|
||||
INSERT INTO `Controls` VALUES (NULL,'Amcrest HTTP API','Ffmpeg','Amcrest_HTTP',0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,5,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,5);
|
||||
INSERT INTO `Controls` (`Name`,`Type`,`Protocol`,`CanReset`,`CanReboot`,`CanZoom`,`CanZoomCon`,`HasPresets`,`NumPresets`,`HasHomePreset`,`CanSetPresets`,`CanMove`,`CanMoveDiag`,`CanMoveCon`,`CanPan`,`CanTilt`) VALUES ('Dahua/Amcrest RPC','Ffmpeg','Dahua_RPC',1,1,1,1,1,25,1,1,1,1,1,1,1);
|
||||
INSERT INTO `Controls` (`Name`,`Type`,`Protocol`,`CanReset`,`CanReboot`,`CanZoom`,`CanZoomCon`,`HasPresets`,`NumPresets`,`HasHomePreset`,`CanSetPresets`,`CanMove`,`CanMoveDiag`,`CanMoveCon`,`CanPan`,`CanTilt`) VALUES ('Amcrest ASH21-B RPC','Ffmpeg','Dahua_RPC',1,1,0,0,0,0,0,0,1,0,1,1,1);
|
||||
INSERT INTO `Controls` (`Name`,`Type`,`Protocol`,`CanReset`,`CanReboot`,`CanZoom`,`CanZoomCon`,`HasPresets`,`NumPresets`,`HasHomePreset`,`CanSetPresets`,`CanMove`,`CanMoveDiag`,`CanMoveCon`,`CanPan`,`CanTilt`,`CanLight`) VALUES ('Amcrest ADC2W RPC','Ffmpeg','Dahua_RPC',1,1,0,0,0,0,0,0,0,0,0,0,0,1);
|
||||
INSERT INTO `Controls` (`Name`,`Type`,`Protocol`,`CanReset`,`CanReboot`,`CanZoom`,`CanZoomCon`,`HasPresets`,`NumPresets`,`HasHomePreset`,`CanSetPresets`,`CanMove`,`CanMoveDiag`,`CanMoveCon`,`CanPan`,`CanTilt`,`CanLight`,`CanIndicatorLight`) VALUES ('Dahua/Amcrest RPC','Ffmpeg','Dahua_RPC',1,1,1,1,1,25,1,1,1,1,1,1,1,1,1);
|
||||
INSERT INTO `Controls` (`Name`,`Type`,`Protocol`,`CanReset`,`CanReboot`,`CanZoom`,`CanZoomCon`,`HasPresets`,`NumPresets`,`HasHomePreset`,`CanSetPresets`,`CanMove`,`CanMoveDiag`,`CanMoveCon`,`CanPan`,`CanTilt`,`CanIndicatorLight`) VALUES ('Amcrest ASH21-B RPC','Ffmpeg','Dahua_RPC',1,1,0,0,0,0,0,0,1,0,1,1,1,1);
|
||||
INSERT INTO `Controls` (`Name`,`Type`,`Protocol`,`CanReset`,`CanReboot`,`CanZoom`,`CanZoomCon`,`HasPresets`,`NumPresets`,`HasHomePreset`,`CanSetPresets`,`CanMove`,`CanMoveDiag`,`CanMoveCon`,`CanPan`,`CanTilt`,`CanLight`,`CanIndicatorLight`) VALUES ('Amcrest ADC2W RPC','Ffmpeg','Dahua_RPC',1,1,0,0,0,0,0,0,0,0,0,0,0,1,1);
|
||||
INSERT INTO `Controls` (`Name`,`Type`,`Protocol`,`CanReset`,`CanReboot`,`CanIndicatorLight`) VALUES ('Amcrest ASH42-B RPC','Ffmpeg','Dahua_RPC',1,1,1);
|
||||
INSERT INTO `Controls` VALUES (NULL,'ONVIF','Ffmpeg','ONVIF',0,0,1,1,1,0,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,0,1,0,0,0,100,1,1,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,0,1,0,0,0,100,1,1,0,NULL,NULL,1,20,1,1,1,1,1,0,1,1,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0);
|
||||
|
||||
--
|
||||
|
||||
39
db/zm_update-1.39.14.sql
Normal file
39
db/zm_update-1.39.14.sql
Normal file
@@ -0,0 +1,39 @@
|
||||
--
|
||||
-- Add CanIndicatorLight capability column to Controls (camera indicator LED on/off).
|
||||
--
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'Controls'
|
||||
AND column_name = 'CanIndicatorLight') > 0,
|
||||
'SELECT ''Column CanIndicatorLight already exists''',
|
||||
'ALTER TABLE `Controls` ADD COLUMN `CanIndicatorLight` tinyint(3) unsigned NOT NULL default ''0'' AFTER `CanLight`'
|
||||
));
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
--
|
||||
-- Add model-specific Controls entry for the Amcrest ASH42-B.
|
||||
-- This model has no PTZ; its indicator LED is controlled via LightGlobal config
|
||||
-- (configManager.setConfig/getConfig, verified live). Reboot via magicBox.reboot.
|
||||
--
|
||||
INSERT INTO `Controls`
|
||||
(`Name`,`Type`,`Protocol`,`CanReset`,`CanReboot`,`CanIndicatorLight`)
|
||||
SELECT 'Amcrest ASH42-B RPC','Ffmpeg','Dahua_RPC',1,1,1
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `Controls` WHERE `Name`='Amcrest ASH42-B RPC');
|
||||
|
||||
--
|
||||
-- The ASH21-B and ADC2W also control their indicator LED via LightGlobal
|
||||
-- (verified live on both models), so enable the capability on their entries.
|
||||
--
|
||||
UPDATE `Controls` SET `CanIndicatorLight`=1
|
||||
WHERE `Name` IN ('Amcrest ASH21-B RPC','Amcrest ADC2W RPC');
|
||||
|
||||
--
|
||||
-- The generic entry keeps every capability enabled for testing new cameras.
|
||||
-- (CanLight column was added by 1.39.12, CanIndicatorLight above.)
|
||||
--
|
||||
UPDATE `Controls` SET `CanLight`=1, `CanIndicatorLight`=1
|
||||
WHERE `Name`='Dahua/Amcrest RPC';
|
||||
@@ -67,7 +67,10 @@ sub open {
|
||||
$self->{state} = 'closed';
|
||||
return undef;
|
||||
}
|
||||
my $port = $self->{port} || 80;
|
||||
# RPC2 is always on the camera's web port (default 80), not the stream port.
|
||||
# parse_Path() derives {port} from the RTSP URL (typically 554), which is
|
||||
# wrong for RPC. Only honour {port} if it looks like a web port.
|
||||
my $port = ($self->{port} && $self->{port} != 554) ? $self->{port} : 80;
|
||||
# JSON-RPC auth is in-band; do not put userinfo in the URL.
|
||||
$self->{RPCBase} = 'http://'.$self->{host}.':'.$port.'/';
|
||||
$self->{rpc_id} = 0;
|
||||
@@ -299,15 +302,51 @@ sub coaxial_white {
|
||||
sub lightOn { my $self = shift; $self->coaxial_white(1); }
|
||||
sub lightOff { my $self = shift; $self->coaxial_white(2); }
|
||||
|
||||
# Indicator LED (camera status light) via LightGlobal config.
|
||||
# Used on models like the ASH42-B where LightGlobal.Enable controls the indicator.
|
||||
sub indicatorLightOn { my $self = shift; $self->set_config({ name => 'LightGlobal', table => [ { Enable => JSON::MaybeXS::true } ] }); }
|
||||
sub indicatorLightOff { my $self = shift; $self->set_config({ name => 'LightGlobal', table => [ { Enable => JSON::MaybeXS::false } ] }); }
|
||||
|
||||
sub indicatorLightStatus {
|
||||
my $self = shift;
|
||||
my $r = $self->rpc_call('configManager.getConfig', { name => 'LightGlobal' });
|
||||
if (!$r || !$r->{result}) {
|
||||
if ($r && $self->session_expired($r->{error})) {
|
||||
return { Enable => undef } if !$self->login();
|
||||
$r = $self->rpc_call('configManager.getConfig', { name => 'LightGlobal' });
|
||||
}
|
||||
}
|
||||
my $enable = ($r and $r->{result} and $r->{params} and $r->{params}{table})
|
||||
? $r->{params}{table}[0]{Enable} : undef;
|
||||
return { Enable => (defined $enable ? ($enable ? 'On' : 'Off') : undef) };
|
||||
}
|
||||
|
||||
# Query the current white-light state. Returns { WhiteLight => 'On'|'Off'|undef }.
|
||||
# Used by the status-aware single-button toggle in the web UI.
|
||||
sub lightStatus {
|
||||
my $self = shift;
|
||||
my $r = $self->rpc_call('CoaxialControlIO.getStatus', { channel => 0 });
|
||||
if (!$r || !$r->{result}) {
|
||||
if ($r && $self->session_expired($r->{error})) {
|
||||
return { WhiteLight => undef } if !$self->login();
|
||||
$r = $self->rpc_call('CoaxialControlIO.getStatus', { channel => 0 });
|
||||
}
|
||||
}
|
||||
my $status = ($r and ref($r) eq 'HASH' and $r->{params}) ? $r->{params}{status} : undef;
|
||||
return { WhiteLight => ($status ? $status->{WhiteLight} : undef) };
|
||||
}
|
||||
|
||||
# Send a keepalive ping to prevent session expiry in long-running daemons.
|
||||
# Call periodically (every ~30s) from the control daemon's idle loop.
|
||||
sub keepAlive {
|
||||
my $self = shift;
|
||||
my $r = $self->rpc_call('global.keepAlive', { timeout => 20 });
|
||||
if (!$r || !$r->{result}) {
|
||||
Debug('Dahua_RPC: keepAlive failed, re-logging in');
|
||||
$self->login();
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Config / probe interface
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -357,6 +396,18 @@ sub set_config {
|
||||
my ($self, $diff) = @_;
|
||||
return undef unless ref($diff) eq 'HASH' and $diff->{name} and exists $diff->{table};
|
||||
my $r = $self->rpc_call('configManager.setConfig', $diff);
|
||||
if (!$r || !$r->{result}) {
|
||||
my $msg = $r ? ($r->{error}{message} // 'result=false') : 'no response';
|
||||
Error('Dahua_RPC: set_config('.$diff->{name}.') failed: '.$msg);
|
||||
# Re-login on session expiry and retry once (same pattern as ptz_raw).
|
||||
if ($r && $self->session_expired($r->{error})) {
|
||||
Debug('Dahua_RPC: session expired during set_config, re-logging in');
|
||||
return undef if !$self->login();
|
||||
$r = $self->rpc_call('configManager.setConfig', $diff);
|
||||
Error('Dahua_RPC: set_config('.$diff->{name}.') still failed after re-login')
|
||||
if !$r || !$r->{result};
|
||||
}
|
||||
}
|
||||
return $r ? $r->{result} : undef;
|
||||
}
|
||||
|
||||
|
||||
@@ -197,7 +197,9 @@ if ($options{command}) {
|
||||
vec($rin, fileno(SERVER), 1) = 1;
|
||||
my $win = $rin;
|
||||
my $ein = $win;
|
||||
my $timeout = MAX_COMMAND_WAIT;
|
||||
# Use a short timeout if the module supports keepAlive so the session
|
||||
# does not expire between commands (Dahua cameras expire after ~60s idle).
|
||||
my $timeout = $control->can('keepAlive') ? 30 : MAX_COMMAND_WAIT;
|
||||
while (!$zm_terminate) {
|
||||
my $nfound = select(my $rout = $rin, undef, undef, $timeout);
|
||||
if ( $nfound > 0 ) {
|
||||
@@ -246,6 +248,7 @@ if ($options{command}) {
|
||||
}
|
||||
} else {
|
||||
Debug('Select timed out');
|
||||
$control->keepAlive() if $control->can('keepAlive');
|
||||
}
|
||||
} # end while !$zm_terminate
|
||||
Info("Control server $id/$protocol exiting");
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.39.13
|
||||
1.39.14
|
||||
|
||||
@@ -99,6 +99,7 @@ class Control extends ZM_Object {
|
||||
'MinWhiteSpeed' => NULL,
|
||||
'MaxWhiteSpeed' => NULL,
|
||||
'CanLight' => 0,
|
||||
'CanIndicatorLight' => 0,
|
||||
'HasPresets' => 0,
|
||||
'NumPresets' => 0,
|
||||
'HasHomePreset' => 0,
|
||||
@@ -125,6 +126,8 @@ class Control extends ZM_Object {
|
||||
$cmds['Reboot'] = 'reboot';
|
||||
$cmds['LightOn'] = 'lightOn';
|
||||
$cmds['LightOff'] = 'lightOff';
|
||||
$cmds['IndicatorLightOn'] = 'indicatorLightOn';
|
||||
$cmds['IndicatorLightOff'] = 'indicatorLightOff';
|
||||
|
||||
$cmds['PresetSet'] = 'presetSet';
|
||||
$cmds['PresetGoto'] = 'presetGoto';
|
||||
|
||||
@@ -205,6 +205,8 @@ $SLANG = array(
|
||||
'CanWhite' => 'Can White Balance',
|
||||
'CanWhiteCon' => 'Can White Bal. Continuous',
|
||||
'CanLight' => 'Can Light',
|
||||
'CanIndicatorLight' => 'Can Indicator Light',
|
||||
'Indicator' => 'Indicator',
|
||||
'CanWhiteRel' => 'Can White Bal. Relative',
|
||||
'CanZoomAbs' => 'Can Zoom Absolute',
|
||||
'CanZoom' => 'Can Zoom',
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
}
|
||||
|
||||
/* Lit/amber when the camera reports the light is on; clicking then turns it off. */
|
||||
.ptzControls .controlsPanel .lightToggleBtn.active {
|
||||
.ptzControls .controlsPanel .lightToggleBtn.active,
|
||||
.ptzControls .controlsPanel .indicatorLightToggleBtn.active {
|
||||
background-color: #ffc107;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
@@ -124,6 +124,16 @@ function controlLight($monitor, $cmds) {
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
function controlIndicatorLight($monitor, $cmds) {
|
||||
ob_start();
|
||||
?>
|
||||
<div class="lightControls">
|
||||
<button type="button" class="ptzTextBtn indicatorLightToggleBtn" data-on-click="controlCmd" data-on-cmd="<?php echo $cmds['IndicatorLightOn'] ?>" data-off-cmd="<?php echo $cmds['IndicatorLightOff'] ?>" value="<?php echo $cmds['IndicatorLightOn'] ?>"><?php echo translate('Indicator') ?></button>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
function controlPanTilt($monitor, $cmds) {
|
||||
$control = $monitor->Control();
|
||||
ob_start();
|
||||
@@ -253,6 +263,8 @@ function ptzControls($monitor) {
|
||||
echo controlWhite($monitor, $cmds);
|
||||
if ( $control->CanLight() )
|
||||
echo controlLight($monitor, $cmds);
|
||||
if ( $control->CanIndicatorLight() )
|
||||
echo controlIndicatorLight($monitor, $cmds);
|
||||
if ( $control->CanMove() ) {
|
||||
?>
|
||||
<div class="pantiltPanel">
|
||||
|
||||
@@ -47,6 +47,7 @@ $controls = dbFetchAll('SELECT * FROM Controls ORDER BY Name');
|
||||
<th class="colCanIris" data-sortable="true" data-field="CanIris"><?php echo translate('CanIris') ?></th>
|
||||
<th class="colCanWhiteBal" data-sortable="true" data-field="CanWhiteBal"><?php echo translate('CanWhiteBal') ?></th>
|
||||
<th class="colCanLight" data-sortable="true" data-field="CanLight"><?php echo translate('CanLight') ?></th>
|
||||
<th class="colCanIndicatorLight" data-sortable="true" data-field="CanIndicatorLight"><?php echo translate('CanIndicatorLight') ?></th>
|
||||
<th class="colHasPresets" data-sortable="true" data-field="HasPresets"><?php echo translate('HasPresets') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -66,6 +67,7 @@ foreach( $controls as $control ) {
|
||||
<td class="colCanIris"><?php echo $control['CanIris']?translate('Yes'):translate('No') ?></td>
|
||||
<td class="colCanWhiteBal"><?php echo $control['CanWhite']?translate('Yes'):translate('No') ?></td>
|
||||
<td class="colCanLight"><?php echo $control['CanLight']?translate('Yes'):translate('No') ?></td>
|
||||
<td class="colCanIndicatorLight"><?php echo $control['CanIndicatorLight']?translate('Yes'):translate('No') ?></td>
|
||||
<td class="colHasPresets"><?php echo $control['HasHomePreset']?'H':'' ?><?php echo $control['HasPresets']?$control['NumPresets']:'0' ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
|
||||
@@ -126,6 +126,7 @@ if ( isset($_REQUEST['Control']) ) {
|
||||
'MaxGainSpeed' => '',
|
||||
'CanWhite' => '',
|
||||
'CanLight' => '',
|
||||
'CanIndicatorLight' => '',
|
||||
'CanAutoWhite' => '',
|
||||
'CanWhiteAbs' => '',
|
||||
'CanWhiteRel' => '',
|
||||
@@ -238,6 +239,10 @@ switch ( $name ) {
|
||||
<th class="text-right pr-3" scope="row"><?php echo translate('CanLight') ?></th>
|
||||
<td><input type="checkbox" name="Control[CanLight]" value="1"<?php if ( !empty($Control['CanLight']) ) { ?> checked="checked"<?php } ?>/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-right pr-3" scope="row"><?php echo translate('CanIndicatorLight') ?></th>
|
||||
<td><input type="checkbox" name="Control[CanIndicatorLight]" value="1"<?php if ( !empty($Control['CanIndicatorLight']) ) { ?> checked="checked"<?php } ?>/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-right pr-3" scope="row"><?php echo translate('CanReset') ?></th>
|
||||
<td><input type="checkbox" name="Control[CanReset]" value="1"<?php if ( !empty($Control['CanReset']) ) { ?> checked="checked"<?php } ?>/></td>
|
||||
|
||||
@@ -423,6 +423,26 @@ function updateLightButton(respObj) {
|
||||
// (un-highlighted, sends the on command) as a plain toggle.
|
||||
}
|
||||
|
||||
function indicatorLightStatusReq() {
|
||||
if (!$j('.indicatorLightToggleBtn').length) return;
|
||||
const data = {control: 'indicatorLightStatus', response: 1};
|
||||
if (auth_hash) data.auth = auth_hash;
|
||||
$j.getJSON(monitorUrl + '?view=request&request=control&id='+monitorId, data)
|
||||
.done(updateIndicatorLightButton)
|
||||
.fail(logAjaxFail);
|
||||
}
|
||||
|
||||
function updateIndicatorLightButton(respObj) {
|
||||
const btn = $j('.indicatorLightToggleBtn');
|
||||
if (!btn.length) return;
|
||||
const state = (respObj && respObj.status) ? respObj.status.Enable : null;
|
||||
if (state == 'On') {
|
||||
btn.addClass('active').val(btn.attr('data-off-cmd'));
|
||||
} else if (state == 'Off') {
|
||||
btn.removeClass('active').val(btn.attr('data-on-cmd'));
|
||||
}
|
||||
}
|
||||
|
||||
function controlCmd(event) {
|
||||
button = event.target;
|
||||
|
||||
@@ -1168,6 +1188,12 @@ function initPage() {
|
||||
setTimeout(lightStatusReq, 800);
|
||||
});
|
||||
}
|
||||
if ($j('.indicatorLightToggleBtn').length) {
|
||||
indicatorLightStatusReq();
|
||||
$j(document).on('click', '.indicatorLightToggleBtn', function() {
|
||||
setTimeout(indicatorLightStatusReq, 800);
|
||||
});
|
||||
}
|
||||
} // initPage
|
||||
|
||||
function watchFullscreen() {
|
||||
|
||||
Reference in New Issue
Block a user