Files
zoneminder/web/includes/control_functions.php
T
Nic BoetandClaude Opus 5.5 99cf213f62 fix: forward control requests to the server that records the monitor
In a multi-server setup a PTZ move, preset or light command sent to one
server for a monitor that another server records never reached the
camera. Monitor::sendControlCommand() passed the zmcontrol option string
to the other server's monitors/daemonControl API, which only starts and
stops zmcontrol.pl, so the request built a URL like
daemonControl/4/ --panspeed=51 ... --command=moveConLeft/zmcontrol.pl.json
and answered "Failed". The web UI's own PTZ buttons send to the
monitor's server and were not affected; clients that send every request
to the server they log in to (zm-py, pyzmNg, zmNinjaNg, aiozoneminder)
and monitor actions whose target is on another server were.

ajax/control.php now hands such a request, as received, to the index.php
of the server that records the monitor (or the action's target), with
the auth relay in place of the caller's credentials, and answers with
that server's reply. That server checks the permissions and builds the
command itself. A request is forwarded at most once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 10:24:29 -05:00

789 lines
30 KiB
PHP

<?php
require_once('logger.php');
// In a multi-server setup only the server that records a monitor can drive its
// camera: zmcontrol.pl runs there. When that is another server, returns the URL
// that hands it this same control request, or null to handle it here. The other
// server checks the permissions and builds the command itself. A request that
// was forwarded once is handled where it lands, so two servers that disagree
// about the monitor's server don't pass it back and forth.
function remoteControlUrl($monitor, $request) {
if ( !defined('ZM_SERVER_ID') or !ZM_SERVER_ID ) return null;
if ( !$monitor->ServerId() or ($monitor->ServerId() == ZM_SERVER_ID) ) return null;
if ( !empty($request['forwarded']) ) return null;
$server = $monitor->Server();
if ( !$server->Id() ) return null;
// The other server learns who is asking from the auth relay, not from the
// credentials this request came with.
$params = array('view'=>'request', 'request'=>'control');
foreach ( $request as $name => $value ) {
if ( !in_array($name, array('view', 'request', 'token', 'auth', 'user', 'username', 'password', 'pass', 'forwarded')) )
$params[$name] = $value;
}
$params['forwarded'] = 1;
$url = $server->UrlToIndex().'?'.http_build_query($params);
$auth_relay = get_auth_relay();
if ( $auth_relay ) $url .= '&'.$auth_relay;
return $url;
}
// Sends a control request to the server that records the monitor and returns
// its JSON reply, or false when that server can't be reached.
function forwardControlRequest($url) {
$context = stream_context_create(array(
'ssl'=>array('verify_peer'=>false, 'verify_peer_name'=>false),
));
$reply = @file_get_contents($url, false, $context);
if ( $reply === false ) {
ZM\Error('Could not forward the control request to '.strtok($url, '?'));
}
return $reply;
}
function buildControlCommand($monitor) {
$ctrlCommand = '';
$control = $monitor->Control();
// control names the control module method zmcontrol calls, and the options are parsed back
// out of this string by spaces, so refuse anything that is not a plain method name.
// refs GHSA-qcm7-vq92-f86f
if ( !isset($_REQUEST['control']) or !preg_match('/^[A-Za-z][A-Za-z0-9_]*$/', $_REQUEST['control']) ) {
ZM\Warning('Invalid control command');
return '';
}
if ( isset($_REQUEST['xge']) || isset($_REQUEST['yge']) ) {
$slow = 0.9; // Threshold for slow speed/timeouts
$turbo = 0.9; // Threshold for turbo speed
$speed = 0;
$xFactor = empty($_REQUEST['xge'])?0:$_REQUEST['xge']/100;
$yFactor = empty($_REQUEST['yge'])?0:$_REQUEST['yge']/100;
if ( preg_match('/^([a-z]+)([A-Z][a-z]+)([A-Za-z]+)+$/', $_REQUEST['control'], $matches) ) {
$command = $matches[1];
$mode = $matches[2];
$dirn = $matches[3];
switch ($command) {
case 'focus' :
if ($control->HasFocusSpeed()) {
$speed = intval(round($control->MinFocusSpeed()+(($control->MaxFocusSpeed()-$control->MinFocusSpeed())*$yFactor)));
$ctrlCommand .= ' --speed='.$speed;
}
switch ($mode) {
case 'Abs' :
case 'Rel' :
$step = intval(round($control->MinFocusStep()+(($control->MaxFocusStep()-$control->MinFocusStep())*$yFactor)));
$ctrlCommand .= ' --step='.$step;
break;
case 'Con' :
if ( $monitor->AutoStopTimeout() ) {
$slowSpeed = intval(round($control->MinFocusSpeed()+(($control->MaxFocusSpeed()-$control->MinFocusSpeed())*$slow)));
if ( $speed < $slowSpeed ) $ctrlCommand .= ' --autostop';
}
break;
}
break;
case 'zoom' :
if ($control->HasZoomSpeed()) {
$speed = intval(round($control->MinZoomSpeed()+(($control->MaxZoomSpeed()-$control->MinZoomSpeed())*$yFactor)));
$ctrlCommand .= ' --speed='.$speed;
}
switch ($mode) {
case 'Abs' :
case 'Rel' :
$step = intval(round($control->MinZoomStep()+(($control->MaxZoomStep()-$control->MinZoomStep())*$yFactor)));
$ctrlCommand .= ' --step='.$step;
break;
case 'Con' :
if ( $monitor->AutoStopTimeout() ) {
$slowSpeed = intval(round($control->MinZoomSpeed()+(($control->MaxZoomSpeed()-$control->MinZoomSpeed())*$slow)));
if ($speed < $slowSpeed) $ctrlCommand .= ' --autostop';
}
break;
}
break;
case 'iris' :
if ($control->HasIrisSpeed()) {
$speed = intval(round($control->MinIrisSpeed()+(($control->MaxIrisSpeed()-$control->MinIrisSpeed())*$yFactor)));
$ctrlCommand .= ' --speed='.$speed;
}
switch ($mode) {
case 'Abs' :
case 'Rel' :
$step = intval(round($control->MinIrisStep()+(($control->MaxIrisStep()-$control->MinIrisStep())*$yFactor)));
$ctrlCommand .= ' --step='.$step;
break;
}
break;
case 'white' :
if ($control->HasWhiteSpeed()) {
$speed = intval(round($control->MinWhiteSpeed()+(($control->MaxWhiteSpeed()-$control->MinWhiteSpeed())*$yFactor)));
$ctrlCommand .= ' --speed='.$speed;
}
switch ($mode) {
case 'Abs' :
case 'Rel' :
$step = intval(round($control->MinWhiteStep()+(($control->MaxWhiteStep()-$control->MinWhiteStep())*$yFactor)));
$ctrlCommand .= ' --step='.$step;
break;
}
break;
case 'gain' :
if ($control->HasGainSpeed()) {
$speed = intval(round($control->MinGainSpeed()+(($control->MaxGainSpeed()-$control->MinGainSpeed())*$yFactor)));
$ctrlCommand .= ' --speed='.$speed;
}
switch ($mode) {
case 'Abs' :
case 'Rel' :
$step = intval(round($control->MinGainStep()+(($control->MaxGainStep()-$control->MinGainStep())*$yFactor)));
$ctrlCommand .= ' --step='.$step;
break;
}
break;
case 'move' :
if ( $monitor->Orientation() != 'ROTATE_0' ) {
$conversions = array(
'ROTATE_90' => array(
'Up' => 'Left',
'Down' => 'Right',
'Left' => 'Down',
'Right' => 'Up',
'UpLeft' => 'DownLeft',
'UpRight' => 'UpLeft',
'DownLeft' => 'DownRight',
'DownRight' => 'UpRight',
),
'ROTATE_180' => array(
'Up' => 'Down',
'Down' => 'Up',
'Left' => 'Right',
'Right' => 'Left',
'UpLeft' => 'DownRight',
'UpRight' => 'DownLeft',
'DownLeft' => 'UpRight',
'DownRight' => 'UpLeft',
),
'ROTATE_270' => array(
'Up' => 'Right',
'Down' => 'Left',
'Left' => 'Up',
'Right' => 'Down',
'UpLeft' => 'UpRight',
'UpRight' => 'DownRight',
'DownLeft' => 'UpLeft',
'DownRight' => 'DownLeft',
),
'FLIP_HORI' => array(
'Up' => 'Up',
'Down' => 'Down',
'Left' => 'Right',
'Right' => 'Left',
'UpLeft' => 'UpRight',
'UpRight' => 'UpLeft',
'DownLeft' => 'DownRight',
'DownRight' => 'DownLeft',
),
'FLIP_VERT' => array(
'Up' => 'Down',
'Down' => 'Up',
'Left' => 'Left',
'Right' => 'Right',
'UpLeft' => 'DownLeft',
'UpRight' => 'DownRight',
'DownLeft' => 'UpLeft',
'DownRight' => 'UpRight',
),
);
$new_dirn = $conversions[$monitor->Orientation()][$dirn];
$_REQUEST['control'] = preg_replace( "/_$dirn\$/", "_$new_dirn", $_REQUEST['control'] );
$dirn = $new_dirn;
}
if ( $control->HasPanSpeed() && $xFactor ) {
if ( $control->HasTurboPan() ) {
if ( $xFactor >= $turbo ) {
$panSpeed = $control->TurboPanSpeed();
} else {
$xFactor = $xFactor/$turbo;
$panSpeed = intval(round($control->MinPanSpeed()+(($control->MaxPanSpeed()-$control->MinPanSpeed())*$xFactor)));
}
} else {
$panSpeed = intval(round($control->MinPanSpeed()+(($control->MaxPanSpeed()-$control->MinPanSpeed())*$xFactor)));
}
$ctrlCommand .= ' --panspeed='.$panSpeed;
}
if ( $control->HasTiltSpeed() && $yFactor ) {
if ( $control->HasTurboTilt() ) {
if ( $yFactor >= $turbo ) {
$tiltSpeed = $control->TurboTiltSpeed();
} else {
$yFactor = $yFactor/$turbo;
$tiltSpeed = intval(round($control->MinTiltSpeed()+(($control->MaxTiltSpeed()-$control->MinTiltSpeed())*$yFactor)));
}
} else {
$tiltSpeed = intval(round($control->MinTiltSpeed()+(($control->MaxTiltSpeed()-$control->MinTiltSpeed())*$yFactor)));
}
$ctrlCommand .= ' --tiltspeed='.$tiltSpeed;
}
switch( $mode ) {
case 'Rel' :
case 'Abs' :
if ( preg_match( '/(Left|Right)$/', $dirn ) ) {
$panStep = intval(round($control->MinPanStep()+(($control->MaxPanStep()-$control->MinPanStep())*$xFactor)));
$ctrlCommand .= ' --panstep='.$panStep;
}
if ( preg_match( '/^(Up|Down)/', $dirn ) ) {
$tiltStep = intval(round($control->MinTiltStep()+(($control->MaxTiltStep()-$control->MinTiltStep())*$yFactor)));
$ctrlCommand .= ' --tiltstep='.$tiltStep;
}
break;
case 'Con' :
if ( $monitor->AutoStopTimeout() ) {
$slowPanSpeed = intval(round($control->MinPanSpeed()+(($control->MaxPanSpeed()-$control->MinPanSpeed())*$slow)));
$slowTiltSpeed = intval(round($control->MinTiltSpeed()+(($control->MaxTiltSpeed()-$control->MinTiltSpeed())*$slow)));
if ( (!isset($panSpeed) || ($panSpeed < $slowPanSpeed)) && (!isset($tiltSpeed) || ($tiltSpeed < $slowTiltSpeed)) ) {
$ctrlCommand .= ' --autostop';
}
}
break;
}
}
} else {
ZM\Error('Invalid control parameter: ' . $_REQUEST['control'] );
}
} elseif ( isset($_REQUEST['x']) && isset($_REQUEST['y']) ) {
if ( $_REQUEST['control'] == 'moveMap' ) {
$x = deScale( $_REQUEST['x'], $_REQUEST['scale'] );
$y = deScale( $_REQUEST['y'], $_REQUEST['scale'] );
switch ( $monitor->Orientation() ) {
case 'ROTATE_0' :
case 'ROTATE_180' :
case 'FLIP_HORI' :
case 'FLIP_VERT' :
$width = $monitor->Width();
$height = $monitor->Height();
break;
case 'ROTATE_90' :
case 'ROTATE_270' :
$width = $monitor->Height();
$height = $monitor->Width();
break;
}
switch ( $monitor->Orientation() ) {
case 'ROTATE_90' :
$tempY = $y;
$y = $height - $x;
$x = $tempY;
break;
case 'ROTATE_180' :
$x = $width - $x;
$y = $height - $y;
break;
case 'ROTATE_270' :
$tempX = $x;
$x = $width - $y;
$y = $tempX;
break;
case 'FLIP_HORI' :
$x = $width - $x;
break;
case 'FLIP_VERT' :
$y = $height - $y;
break;
}
//$ctrlCommand .= " --xcoord=$x --ycoord=$y --width=$width --height=$height";
$ctrlCommand .= " --xcoord=$x --ycoord=$y";
} elseif ( $_REQUEST['control'] == 'movePseudoMap' ) {
$x = deScale( $_REQUEST['x'], $_REQUEST['scale'] );
$y = deScale( $_REQUEST['y'], $_REQUEST['scale'] );
$halfWidth = $monitor->Width() / 2;
$halfHeight = $monitor->Height() / 2;
$xFactor = ($x - $halfWidth)/$halfWidth;
$yFactor = ($y - $halfHeight)/$halfHeight;
switch ( $monitor->Orientation() ) {
case 'ROTATE_90' :
$tempYFactor = $y;
$yFactor = -$xFactor;
$xFactor = $tempYFactor;
break;
case 'ROTATE_180' :
$xFactor = -$xFactor;
$yFactor = -$yFactor;
break;
case 'ROTATE_270' :
$tempXFactor = $x;
$xFactor = -$yFactor;
$yFactor = $tempXFactor;
break;
case 'FLIP_HORI' :
$xFactor = -$xFactor;
break;
case 'FLIP_VERT' :
$yFactor = -$yFactor;
break;
}
$turbo = 0.9; // Threshold for turbo speed
$blind = 0.1; // Threshold for blind spot
$panControl = '';
$tiltControl = '';
if ( $xFactor > $blind ) {
$panControl = 'Right';
} elseif ( $xFactor < -$blind ) {
$panControl = 'Left';
}
if ( $yFactor > $blind ) {
$tiltControl = 'Down';
} elseif ( $yFactor < -$blind ) {
$tiltControl = 'Up';
}
$dirn = $tiltControl.$panControl;
if ( !$dirn ) {
// No command, probably in blind spot in middle
$_REQUEST['control'] = 'null';
return( false );
} else {
$_REQUEST['control'] = 'moveRel'.$dirn;
$xFactor = abs($xFactor);
$yFactor = abs($yFactor);
if ( $control->HasPanSpeed() && $xFactor ) {
if ( $control->HasTurboPan() ) {
if ( $xFactor >= $turbo ) {
$panSpeed = $control->TurboPanSpeed();
} else {
$xFactor = $xFactor/$turbo;
$panSpeed = intval(round($control->MinPanSpeed()+(($control->MaxPanSpeed()-$control->MinPanSpeed())*$xFactor)));
}
} else {
$panSpeed = intval(round($control->MinPanSpeed()+(($control->MaxPanSpeed()-$control->MinPanSpeed())*$xFactor)));
}
}
if ( $control->HasTiltSpeed() && $yFactor ) {
if ( $control->HasTurboTilt() ) {
if ( $yFactor >= $turbo ) {
$tiltSpeed = $control->TurboTiltSpeed();
} else {
$yFactor = $yFactor/$turbo;
$tiltSpeed = intval(round($control->MinTiltSpeed()+(($control->MaxTiltSpeed()-$control->MinTiltSpeed())*$yFactor)));
}
} else {
$tiltSpeed = intval(round($control->MinTiltSpeed()+(($control->MaxTiltSpeed()-$control->MinTiltSpeed())*$yFactor)));
}
}
if ( preg_match( '/(Left|Right)$/', $dirn ) ) {
$panStep = intval(round($control->MinPanStep()+(($control->MaxPanStep()-$control->MinPanStep())*$xFactor)));
$ctrlCommand .= ' --panstep='.$panStep.' --panspeed='.$panSpeed;
}
if ( preg_match( '/^(Up|Down)/', $dirn ) ) {
$tiltStep = intval(round($control->MinTiltStep()+(($control->MaxTiltStep()-$control->MinTiltStep())*$yFactor)));
$ctrlCommand .= ' --tiltstep='.$tiltStep.' --tiltspeed='.$tiltSpeed;
}
}
} elseif ( $_REQUEST['control'] == 'moveConMap' ) {
$x = deScale( $_REQUEST['x'], $_REQUEST['scale'] );
$y = deScale( $_REQUEST['y'], $_REQUEST['scale'] );
$halfWidth = $monitor->Width() / 2;
$halfHeight = $monitor->Height() / 2;
$xFactor = ($x - $halfWidth)/$halfWidth;
$yFactor = ($y - $halfHeight)/$halfHeight;
switch ( $monitor->Orientation() ) {
case 'ROTATE_90' :
$tempYFactor = $y;
$yFactor = -$xFactor;
$xFactor = $tempYFactor;
break;
case 'ROTATE_180' :
$xFactor = -$xFactor;
$yFactor = -$yFactor;
break;
case 'ROTATE_270' :
$tempXFactor = $x;
$xFactor = -$yFactor;
$yFactor = $tempXFactor;
break;
case 'FLIP_HORI' :
$xFactor = -$xFactor;
break;
case 'FLIP_VERT' :
$yFactor = -$yFactor;
break;
}
$slow = 0.9; // Threshold for slow speed/timeouts
$turbo = 0.9; // Threshold for turbo speed
$blind = 0.1; // Threshold for blind spot
$panControl = '';
$tiltControl = '';
if ( $xFactor > $blind ) {
$panControl = 'Right';
} elseif ( $xFactor < -$blind ) {
$panControl = 'Left';
}
if ( $yFactor > $blind ) {
$tiltControl = 'Down';
} elseif ( $yFactor < -$blind ) {
$tiltControl = 'Up';
}
$dirn = $tiltControl.$panControl;
if ( !$dirn ) {
// No command, probably in blind spot in middle
$_REQUEST['control'] = 'moveStop';
} else {
$_REQUEST['control'] = 'moveCon'.$dirn;
$xFactor = abs($xFactor);
$yFactor = abs($yFactor);
if ( $control->HasPanSpeed() && $xFactor ) {
if ( $control->HasTurboPan() ) {
if ( $xFactor >= $turbo ) {
$panSpeed = $control->TurboPanSpeed();
} else {
$xFactor = $xFactor/$turbo;
$panSpeed = intval(round($control->MinPanSpeed()+(($control->MaxPanSpeed()-$control->MinPanSpeed())*$xFactor)));
}
} else {
$panSpeed = intval(round($control->MinPanSpeed()+(($control->MaxPanSpeed()-$control->MinPanSpeed())*$xFactor)));
}
}
if ( $control->HasTiltSpeed() && $yFactor ) {
if ( $control->HasTurboTilt() ) {
if ( $yFactor >= $turbo ) {
$tiltSpeed = $control->TurboTiltSpeed();
} else {
$yFactor = $yFactor/$turbo;
$tiltSpeed = intval(round($control->MinTiltSpeed()+(($control->MaxTiltSpeed()-$control->MinTiltSpeed())*$yFactor)));
}
} else {
$tiltSpeed = intval(round($control->MinTiltSpeed()+(($control->MaxTiltSpeed()-$control->MinTiltSpeed())*$yFactor)));
}
}
if ( preg_match( '/(Left|Right)$/', $dirn ) ) {
$ctrlCommand .= ' --panspeed='.$panSpeed;
}
if ( preg_match( '/^(Up|Down)/', $dirn ) ) {
$ctrlCommand .= ' --tiltspeed='.$tiltSpeed;
}
if ( $monitor->AutoStopTimeout() ) {
$slowPanSpeed = intval(round($control->MinPanSpeed()+(($control->MaxPanSpeed()-$control->MinPanSpeed())*$slow)));
$slowTiltSpeed = intval(round($control->MinTiltSpeed()+(($control->MaxTiltSpeed()-$control->MinTiltSpeed())*$slow)));
if ( (!isset($panSpeed) || ($panSpeed < $slowPanSpeed)) && (!isset($tiltSpeed) || ($tiltSpeed < $slowTiltSpeed)) ) {
$ctrlCommand .= ' --autostop';
}
}
}
} else {
$slow = 0.9; // Threshold for slow speed/timeouts
$turbo = 0.9; // Threshold for turbo speed
$long_y = 48;
$short_x = 32;
$short_y = 32;
if ( preg_match( '/^([a-z]+)([A-Z][a-z]+)([A-Z][a-z]+)$/', $_REQUEST['control'], $matches ) ) {
$command = $matches[1];
$mode = $matches[2];
$dirn = $matches[3];
switch( $command ) {
case 'focus' :
switch( $dirn ) {
case 'Near' :
$factor = ($long_y-($y+1))/$long_y;
break;
case 'Far' :
$factor = ($y+1)/$long_y;
break;
}
if ( $control->HasFocusSpeed() ) {
$speed = intval(round($control->MinFocusSpeed()+(($control->MaxFocusSpeed()-$control->MinFocusSpeed())*$factor)));
$ctrlCommand .= ' --speed='.$speed;
}
switch( $mode ) {
case 'Abs' :
case 'Rel' :
$step = intval(round($control->MinFocusStep()+(($control->MaxFocusStep()-$control->MinFocusStep())*$factor)));
$ctrlCommand .= ' --step='.$step;
break;
case 'Con' :
if ( $monitor->AutoStopTimeout() ) {
$slowSpeed = intval(round($control->MinFocusSpeed()+(($control->MaxFocusSpeed()-$control->MinFocusSpeed())*$slow)));
if ( $speed < $slowSpeed ) {
$ctrlCommand .= ' --autostop';
}
}
break;
}
break;
case 'zoom' :
switch( $dirn ) {
case 'Tele' :
$factor = ($long_y-($y+1))/$long_y;
break;
case 'Wide' :
$factor = ($y+1)/$long_y;
break;
}
if ( $control->HasZoomSpeed() ) {
$speed = intval(round($control->MinZoomSpeed()+(($control->MaxZoomSpeed()-$control->MinZoomSpeed())*$factor)));
$ctrlCommand .= ' --speed='.$speed;
}
switch( $mode ) {
case 'Abs' :
case 'Rel' :
$step = intval(round($control->MinZoomStep()+(($control->MaxZoomStep()-$control->MinZoomStep())*$factor)));
$ctrlCommand .= ' --step='.$step;
break;
case 'Con' :
if ( $monitor->AutoStopTimeout() ) {
$slowSpeed = intval(round($control->MinZoomSpeed()+(($control->MaxZoomSpeed()-$control->MinZoomSpeed())*$slow)));
if ( $speed < $slowSpeed ) {
$ctrlCommand .= ' --autostop';
}
}
break;
}
break;
case 'iris' :
switch( $dirn ) {
case 'Open' :
$factor = ($long_y-($y+1))/$long_y;
break;
case 'Close' :
$factor = ($y+1)/$long_y;
break;
}
if ( $control->HasIrisSpeed() ) {
$speed = intval(round($control->MinIrisSpeed()+(($control->MaxIrisSpeed()-$control->MinIrisSpeed())*$factor)));
$ctrlCommand .= ' --speed='.$speed;
}
switch( $mode ) {
case 'Abs' :
case 'Rel' :
$step = intval(round($control->MinIrisStep()+(($control->MaxIrisStep()-$control->MinIrisStep())*$factor)));
$ctrlCommand .= ' --step='.$step;
break;
}
break;
case 'white' :
switch( $dirn ) {
case 'In' :
$factor = ($long_y-($y+1))/$long_y;
break;
case 'Out' :
$factor = ($y+1)/$long_y;
break;
}
if ( $control->HasWhiteSpeed() ) {
$speed = intval(round($control->MinWhiteSpeed()+(($control->MaxWhiteSpeed()-$control->MinWhiteSpeed())*$factor)));
$ctrlCommand .= ' --speed='.$speed;
}
switch( $mode ) {
case 'Abs' :
case 'Rel' :
$step = intval(round($control->MinWhiteStep()+(($control->MaxWhiteStep()-$control->MinWhiteStep())*$factor)));
$ctrlCommand .= ' --step='.$step;
break;
}
break;
case 'gain' :
switch( $dirn ) {
case 'Up' :
$factor = ($long_y-($y+1))/$long_y;
break;
case 'Down' :
$factor = ($y+1)/$long_y;
break;
}
if ( $control->HasGainSpeed() ) {
$speed = intval(round($control->MinGainSpeed()+(($control->MaxGainSpeed()-$control->MinGainSpeed())*$factor)));
$ctrlCommand .= ' --speed='.$speed;
}
switch( $mode ) {
case 'Abs' :
case 'Rel' :
$step = intval(round($control->MinGainStep()+(($control->MaxGainStep()-$control->MinGainStep())*$factor)));
$ctrlCommand .= ' --step='.$step;
break;
}
break;
case 'move' :
$xFactor = 0;
$yFactor = 0;
if ( preg_match( '/^Up/', $dirn ) ) {
$yFactor = ($short_y-($y+1))/$short_y;
} elseif ( preg_match( '/^Down/', $dirn ) ) {
$yFactor = ($y+1)/$short_y;
}
if ( preg_match( '/Left$/', $dirn ) ) {
$xFactor = ($short_x-($x+1))/$short_x;
} elseif ( preg_match( '/Right$/', $dirn ) ) {
$xFactor = ($x+1)/$short_x;
}
if ( $monitor->Orientation() != 'ROTATE_0' ) {
$conversions = array(
'ROTATE_90' => array(
'Up' => 'Left',
'Down' => 'Right',
'Left' => 'Down',
'Right' => 'Up',
'UpLeft' => 'DownLeft',
'UpRight' => 'UpLeft',
'DownLeft' => 'DownRight',
'DownRight' => 'UpRight',
),
'ROTATE_180' => array(
'Up' => 'Down',
'Down' => 'Up',
'Left' => 'Right',
'Right' => 'Left',
'UpLeft' => 'DownRight',
'UpRight' => 'DownLeft',
'DownLeft' => 'UpRight',
'DownRight' => 'UpLeft',
),
'ROTATE_270' => array(
'Up' => 'Right',
'Down' => 'Left',
'Left' => 'Up',
'Right' => 'Down',
'UpLeft' => 'UpRight',
'UpRight' => 'DownRight',
'DownLeft' => 'UpLeft',
'DownRight' => 'DownLeft',
),
'FLIP_HORI' => array(
'Up' => 'Up',
'Down' => 'Down',
'Left' => 'Right',
'Right' => 'Left',
'UpLeft' => 'UpRight',
'UpRight' => 'UpLeft',
'DownLeft' => 'DownRight',
'DownRight' => 'DownLeft',
),
'FLIP_VERT' => array(
'Up' => 'Down',
'Down' => 'Up',
'Left' => 'Left',
'Right' => 'Right',
'UpLeft' => 'DownLeft',
'UpRight' => 'DownRight',
'DownLeft' => 'UpLeft',
'DownRight' => 'UpRight',
),
);
$new_dirn = $conversions[$monitor->Orientation()][$dirn];
$_REQUEST['control'] = preg_replace( "/_$dirn\$/", "_$new_dirn", $_REQUEST['control'] );
$dirn = $new_dirn;
}
if ( $control->HasPanSpeed() && $xFactor ) {
if ( $control->HasTurboPan() ) {
if ( $xFactor >= $turbo ) {
$panSpeed = $control->TurboPanSpeed();
} else {
$xFactor = $xFactor/$turbo;
$panSpeed = intval(round($control->MinPanSpeed()+(($control->MaxPanSpeed()-$control->MinPanSpeed())*$xFactor)));
}
} else {
$panSpeed = intval(round($control->MinPanSpeed()+(($control->MaxPanSpeed()-$control->MinPanSpeed())*$xFactor)));
}
$ctrlCommand .= ' --panspeed='.$panSpeed;
}
if ( $control->HasTiltSpeed() && $yFactor ) {
if ( $control->HasTurboTilt() ) {
if ( $yFactor >= $turbo ) {
$tiltSpeed = $control->TurboTiltSpeed();
} else {
$yFactor = $yFactor/$turbo;
$tiltSpeed = intval(round($control->MinTiltSpeed()+(($control->MaxTiltSpeed()-$control->MinTiltSpeed())*$yFactor)));
}
} else {
$tiltSpeed = intval(round($control->MinTiltSpeed()+(($control->MaxTiltSpeed()-$control->MinTiltSpeed())*$yFactor)));
}
$ctrlCommand .= ' --tiltspeed='.$tiltSpeed;
}
switch( $mode ) {
case 'Rel' :
case 'Abs' :
if ( preg_match( '/(Left|Right)$/', $dirn ) ) {
$panStep = intval(round($control->MinPanStep()+(($control->MaxPanStep()-$control->MinPanStep())*$xFactor)));
$ctrlCommand .= ' --panstep='.$panStep;
}
if ( preg_match( '/^(Up|Down)/', $dirn ) ) {
$tiltStep = intval(round($control->MinTiltStep()+(($control->MaxTiltStep()-$control->MinTiltStep())*$yFactor)));
$ctrlCommand .= ' --tiltstep='.$tiltStep;
}
break;
case 'Con' :
if ( $monitor->AutoStopTimeout() ) {
$slowPanSpeed = intval(round($control->MinPanSpeed()+(($control->MaxPanSpeed()-$control->MinPanSpeed())*$slow)));
$slowTiltSpeed = intval(round($control->MinTiltSpeed()+(($control->MaxTiltSpeed()-$control->MinTiltSpeed())*$slow)));
if ( (!isset($panSpeed) || ($panSpeed < $slowPanSpeed)) && (!isset($tiltSpeed) || ($tiltSpeed < $slowTiltSpeed)) ) {
$ctrlCommand .= ' --autostop';
}
}
break;
}
}
}
}
} else {
if ( preg_match( '/^audioPlay(\d+)$/', $_REQUEST['control'], $matches ) ) {
// The sound to play is carried in the command name, as presets are,
// because a control button has no way to send a separate parameter.
$_REQUEST['control'] = 'audioPlay';
$ctrlCommand .= ' --file='.$matches[1];
} elseif ( preg_match( '/^presetGoto(\d+)$/', $_REQUEST['control'], $matches ) ) {
$_REQUEST['control'] = 'presetGoto';
$ctrlCommand .= ' --preset='.$matches[1];
} elseif ( $_REQUEST['control'] == 'presetGoto' && !empty($_REQUEST['preset']) ) {
// Control modules look the preset up in SQL and send it to the camera.
if ( !ctype_digit((string)$_REQUEST['preset']) ) {
ZM\Warning('Invalid preset');
return '';
}
$ctrlCommand .= ' --preset='.$_REQUEST['preset'];
} elseif ( $_REQUEST['control'] == 'presetSet' ) {
if ( canEdit( 'Control' ) ) {
$preset = validInt($_REQUEST['preset']);
$newLabel = validJsStr($_REQUEST['newLabel']);
$row = dbFetchOne(
'SELECT * FROM `ControlPresets` WHERE `MonitorId` = ? AND `Preset`=?',
NULL, array($monitor->Id(), $preset));
if ( $newLabel != $row['Label'] ) {
if ( $newLabel ) {
dbQuery('REPLACE INTO `ControlPresets` (`MonitorId`, `Preset`, `Label`) VALUES ( ?, ?, ? )',
array($monitor->Id(), $preset, $newLabel));
} else {
dbQuery('DELETE FROM `ControlPresets` WHERE `MonitorId`=? AND `Preset`=?',
array($monitor->Id(), $preset));
}
}
$ctrlCommand .= ' --preset='.$preset;
}
$ctrlCommand .= ' --preset='.$preset;
} elseif ( $_REQUEST['control'] == 'moveMap' ) {
$ctrlCommand .= " --xcoord=$x --ycoord=$y";
}
}
$ctrlCommand .= ' --command='.$_REQUEST['control'];
return $ctrlCommand;
}