mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-09-23 02:55:44 -04:00
An IP speaker is controllable but has none of the capabilities the Controls table models: nothing moves, focuses or lights up. It plays a sound file it already holds, selected by a numeric id, and carries an output volume. Add CanAudioPlay, MinAudioFile, MaxAudioFile and CanAudioVolume to Controls, with migration zm_update-1.39.21.sql. CanAudioPlay renders one button per sound id plus a stop button; MinAudioFile/MaxAudioFile bound that range; CanAudioVolume renders the volume pair. The sound id travels in the command name (audioPlay12), the way presets already do, because a control button has no way to attach a separate parameter. Add ZoneMinder::Control::IPSpeaker driving the device's own HTTP interface, and a Controls entry for it. Playback goes over the vendor interface rather than ONVIF because the ONVIF audio output service on this firmware only describes the output and offers no way to start a stored file. Measured against a device reporting ONVIF Manufacturer "IPSpeaker" and firmware CS20-V3.3.45N: - File ids fall in two windows, 10-14 built-in and 20-30 operator uploads. Anything else is refused with result -2, and an id in a window with no file uploaded with result -3, so valid_fileid refuses only the former: an empty slot is a legitimate id the operator may fill later. - config=audio.set replaces the whole audio section, so a partial write reverts the microphone, codec list and echo-cancellation settings. Every volume change reads the section and echoes it back with the new level. audio.get also returns outmute, which audio.set rejects, so it is excluded from the field list. - The volume reads back and round-trips, but the device's RTSP audio is a pre-volume tap of the playback signal: it is unchanged with outvolume at 0, so it cannot confirm the loudspeaker's acoustic output. CanAudioVolume is set on the strength of the setting persisting, and both the POD and the seed row say the acoustic effect is unconfirmed. The seed row covers the built-in window only, because a fresh device has nothing uploaded and the firmware refuses an empty slot. Adding columns to Controls extends the 43 positional INSERTs in db/controls.sql, which carry no column list and so must match the column count exactly. Tests: scripts/ZoneMinder/t/ip_speaker.t, 32 assertions covering file id windows, volume clamping and stepping, request building and the whole-section write. All pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UvTCzCbvGt8xKQRNCSA7o8 (cherry picked from commit d07e552a3c58373f7f0ccebf9aa28adb4d51baa3)
104 lines
3.1 KiB
PHP
104 lines
3.1 KiB
PHP
<?php
|
|
//
|
|
// ZoneMinder web action file
|
|
// Copyright (C) 2019 ZoneMinder LLC
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
//
|
|
|
|
|
|
if ( !canEdit('Control') ) {
|
|
ZM\Warning('Need Control permissions to edit control capabilities');
|
|
return;
|
|
} // end if !canEdit Controls
|
|
|
|
if ( $action == 'Save' ) {
|
|
require_once('includes/Control.php');
|
|
$Control = new ZM\Control( !empty($_REQUEST['cid']) ? $_REQUEST['cid'] : null );
|
|
|
|
$field_defaults = array(
|
|
'CanWake' => 0,
|
|
'CanSleep' => 0,
|
|
'CanReset' => 0,
|
|
'CanReboot' => 0,
|
|
'CanMove' => 0,
|
|
'CanMoveDiag' => 0,
|
|
'CanMoveMap' => 0,
|
|
'CanMoveRel' => 0,
|
|
'CanMoveAbs' => 0,
|
|
'CanMoveCon' => 0,
|
|
'CanPan' => 0,
|
|
'HasPanSpeed' => 0,
|
|
'HasTurboPan' => 0,
|
|
'CanTilt' => 0,
|
|
'HasTiltSpeed' => 0,
|
|
'HasTurboTilt' => 0,
|
|
'CanZoom' => 0,
|
|
'CanZoomRel' => 0,
|
|
'CanZoomAbs' => 0,
|
|
'CanZoomCon' => 0,
|
|
'HasZoomSpeed' => 0,
|
|
'CanFocus' => 0,
|
|
'CanAutoFocus' => 0,
|
|
'CanFocusRel' => 0,
|
|
'CanFocusAbs' => 0,
|
|
'CanFocusCon' => 0,
|
|
'HasFocusSpeed' => 0,
|
|
'CanGain' => 0,
|
|
'CanAutoGain' => 0,
|
|
'CanGainRel' => 0,
|
|
'CanGainAbs' => 0,
|
|
'CanGainCon' => 0,
|
|
'HasGainSpeed' => 0,
|
|
'CanWhite' => 0,
|
|
'CanAutoWhite' => 0,
|
|
'CanWhiteRel' => 0,
|
|
'CanWhiteAbs' => 0,
|
|
'CanWhiteCon' => 0,
|
|
'HasWhiteSpeed' => 0,
|
|
'CanIris' => 0,
|
|
'CanAutoIris' => 0,
|
|
'CanIrisRel' => 0,
|
|
'CanIrisAbs' => 0,
|
|
'CanIrisCon' => 0,
|
|
'HasIrisSpeed' => 0,
|
|
'HasPresets' => 0,
|
|
'HasHomePreset' => 0,
|
|
'CanSetPresets' => 0,
|
|
'CanLed' => 0,
|
|
'CanLight' => 0,
|
|
'CanAudioPlay' => 0,
|
|
'CanAudioVolume' => 0,
|
|
);
|
|
|
|
# Checkboxes don't return an element in the POST data, so won't be present in newControl.
|
|
# So force a value for these fields
|
|
foreach ( $field_defaults as $field => $value ) {
|
|
if ( ! (isset($_REQUEST['Control'][$field]) and $_REQUEST['Control'][$field]) ) {
|
|
$_REQUEST['Control'][$field] = $value;
|
|
}
|
|
} # end foreach type
|
|
|
|
//$changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns );
|
|
if (!$Control->save($_REQUEST['Control'])) {
|
|
global $error_message;
|
|
$error_message .= "Error saving control: " . $Control->get_last_error().'</br>';
|
|
} else {
|
|
ZM\AuditAction((!empty($_REQUEST['cid']) ? 'update' : 'create'), 'control', $Control->Id(), 'Name: '.($Control->Name() ?? ''));
|
|
$redirect = '?view=options&tab=control';
|
|
}
|
|
} // end if action
|
|
?>
|