¿Mostrar (y administrar) imágenes de webcam?

4

Tengo una cámara web que escribe cada minuto aproximadamente, una imagen en una carpeta ftp en el servidor donde está instalado WP. Mi pregunta es cómo obtener la última imagen que se muestra en la página, actualizar cada 60 segundos y eliminar las más antiguas de esa carpeta.

    
pregunta Valentino Dell'Aica 18.09.2014 - 09:51

1 respuesta

1

Bien, aquí está el código para que las páginas de wordpress redibujen una imagen en una página a medida que se detectan nuevas imágenes (como en el caso de un directorio en el que una cámara web carga automáticamente las imágenes). Se supone lo siguiente:

  1. Está utilizando PHP 5.3 o superior (aunque se modifica fácilmente para no requerirlo)
  2. Tiene las imágenes de su cámara web cargadas en algún lugar accesible directamente a través de la web
  3. No desea colocar varias imágenes de cámara en una sola página o publicación

Estoy seguro de que tiene mucho margen de mejora, pero hace lo siguiente:

  • Le permite configurar diferentes páginas de cámaras

La sección 1 es el archivo principal del complemento. Solo se debe colocar en un archivo php, se le debe asignar un encabezado de complemento y se debe colocar en la raíz de una carpeta de complemento adecuada.

La sección 2 es el archivo javascript. El archivo del complemento principal asume que está en un subdirectorio llamado js y tiene el nombre de archivo web_cam_checker.js .

Sección 1: El archivo PHP

/*
Plugin Name: Whatever You Want Here
Description: Handles updating an image via the heartbeat api for a web cam - in answer to a question on stack exchange
Version: 0.0.1
Author: The Privateer
*/
namespace Privateer\WebCam;

try {
    if ( class_exists('Privateer_Do_Web_Cam_Updates') ) {
        throw new \Exception('Privateer_Do_Web_Cam_Updates already exists!', 10001);
    } else {

        class Privateer_Do_Web_Cam_Updates{

            protected $images_dir;              # file system url images are uploaded to
            protected $images_url;              # web uri images are available at
            protected $image_tag_id;            # id of image to be swapped out on displayed page
            protected $do_purge_images;     # should old files be deleted
            protected $refresh_interval_s;  # how often the cam should refresh
            protected $is_debug;                    # boolean - use debug mode?
            protected $init_retry_ms;           # Time in seconds to wait for initialization each try
            protected $min_init_retries;        # Maximum number of attempts to wait for initialization before quitting

            protected $notices;                 # Any notices issued
            protected $errors;                  # Any errors

            function __construct(
                $image_tag_id = '',
                $images_dir = '',
                $images_url = '',
                $refresh_interval_s = 0,
                $init_retry_ms = 0,
                $min_init_retries = 0,
                $is_debug = false,
                $do_purge_images = false
            ) {

                $this->notices = array();


                $defaults = $this->get_default_settings();

                $this->images_dir = ( empty($images_dir) )? $defaults['images_dir'] : (string) $images_dir;
                $this->validate_images_dir_or_throw();

                $images_url = ( empty($images_url) )? $defaults['images_url'] : (string) $images_url;
                if ( empty( $images_url ) ) {
                    throw new \Exception("URL [{$images_url}] not found. Use _privateer_web_cam_images_url filter to set properly.", 10001);
                } else {
                    $this->images_url = $images_url;
                }

                $image_tag_id = ( empty($image_tag_id) ) ? $defaults['image_tag_id'] : (string) $image_tag_id;
                if ( empty($image_tag_id) ) {
                    throw new \Exception("Image Tag ID empty. Please fix via _privateer_web_cam_image_tag_id filter.", 10001);
                } else {
                    $this->image_tag_id = $image_tag_id;
                }

                $do_purge_images = ( empty($do_purge_images) ) ? $defaults['purge_old_images'] : (bool) $do_purge_images;
                $this->do_purge_images = ( $do_purge_images === true )? true : false;

                # Limitations imposed by wp.heartbeat
                $refresh_interval_s = ( empty( $refresh_interval_s ) )? $defaults['refresh_interval_seconds'] : (int) $refresh_interval_s;
                if ( 5 > $refresh_interval_s ) {
                    $this->notices[] = "Min Refresh Interval is 5 seconds. Adjusted from {$refresh_interval_s} to 5.";
                    $this->refresh_interval_s = 5;
                } else if ( 120 < $refresh_interval_s ) {
                    $this->notices[] = "Max Refresh Interval is 120 seconds. Adjusted from {$refresh_interval_s} to 120.";
                    $this->refresh_interval_s = 120;
                } else {
                    $this->refresh_interval_s = $refresh_interval_s;
                }

                $is_debug = ( is_null($is_debug) )? $defaults['debug'] : (bool) $is_debug;
                $this->is_debug = ( $is_debug )? 1 : 0;

                $init_retry_ms = ( empty( $init_retry_ms ) )? $defaults['init_retry_ms'] : (int) $init_retry_ms;
                if ( 200 > $init_retry_ms ) {
                    $this->notices[] = "Init Retry Time mimimum is 200 milliseconds. Adjusted from {$init_retry_ms} to 200.";
                    $this->init_retry_ms = 200;
                } else {
                    $this->init_retry_ms = $init_retry_ms;
                }

                $min_init_retries = ( empty( $min_init_retries ) )? $defaults['init_min_retries'] : (int) $min_init_retries;
                if ( 1 > $min_init_retries ) {
                    $this->notices[] = "Min Init Retries is 1. Adjusted from {$min_init_retries} to 1.";
                    $this->min_init_retries = 1;
                } else {
                    $this->min_init_retries = $min_init_retries;
                }

            }

            protected function get_default_settings() {
                return array(
                    'images_dir' => plugin_dir_path( __FILE__ ) . 'cam-images',
                    'images_url' => plugin_dir_url( __FILE__ ) . 'cam-images',
                    'image_tag_id' => 'main_cam_image',
                    'purge_old_images' => false,
                    'refresh_interval_seconds' => 30,
                    'debug' => WP_DEBUG,
                    'init_retry_ms' => 500,
                    'init_min_retries' => 10
                );
            }

            protected function validate_images_dir_or_throw() {
                if ( !is_dir( $this->images_dir ) ) {
                    throw new \Exception("Directory [{$this->images_dir}] not found. Use _privateer_web_cam_images_dir filter to set properly.", 10001);
                } else if ( !is_readable( $this->images_dir) ) {
                    throw new \Exception("Directory [{$this->images_dir}] not readable.", 10001);
                }
            }

            # The function that processes received heartbeats via ajax
            # - response: what we will be sending back (filtered)
            # - data: what we received
            # - screen_id: will be 'front' or an admin page
            # Anything returning an error key will tell the javascript to stop
            public function do_process_heartbeat_received( $response, $data, $screen_id ) {
                $r = array();

                $key = 'web_cam_checker_' . $this->image_tag_id;

                if ( 'front' !== "{$screen_id}" ) {
                    $r['error'] = 'Not on front end of site.';
                } else if ( !array_key_exists($key, $data) ) {
                    $r['error'] = "Failed to locate key {$key} in data received";
                } else if ( !array_key_exists('current_image_src', $data["{$key}"]) ) {
                    $r['error'] = "Did not find current_image_src in {$key} data";
                } else {
                    $current = $this->get_current_web_cam_image();
                    $reported = (string) $data["{$key}"]['current_image_src'];
                    if ( "{$current}" == "{$reported}" ) {
                        $r['notice'] = 'Image has not changed';
                    } else {
                        $r['webcam_new_uri'] = "{$current}";
                    }
                }
                $response["{$key}"] = $r;

                return $response;
            }

            protected function get_readable_images_in_image_dir() {

                $this->validate_images_dir_or_throw();

                $images = array();

                if ( $handle = opendir( "{$this->images_dir}" ) ) {
                    while ( false !== ( $file_name = readdir( $handle ) ) ) {
                        switch ( "{$file_name}" ) {
                            case '.':
                            case '..':
                                # Skip current and previous directory links
                                break;
                            default:
                                # Build the full file path to the file found
                                $file_path = "{$this->images_dir}/{$file_name}";
                                if ( is_file( "{$file_path}" ) && is_readable( "{$file_path}" ) ) {
                                    # TODO: Check to be sure it is an image
                                    $images["{$file_name}"] = $file_path;
                                }
                                break;
                        }
                    }
                    @closedir( $handle );
                } else {
                    $this->notices[] = "Failed to open directory {$this->images_dir} for reading.";
                }

                return $images;
            }

            protected function get_newest_image_name($images) {
                $newest_name = '';
                $newest_ts = 0;
                foreach ( $images as $name => $path ) {
                    $last_modified = filectime( $path );
                    if ( $last_modified > $newest_ts ) {
                        $newest_name = $name;
                        $newest_ts = $last_modified;
                    }
                }
                return $newest_name;
            }

            protected function get_current_web_cam_image() {
                $current = '';              # The newest image on the web server

                try {
                    $this->validate_images_dir_or_throw();

                    $images = $this->get_readable_images_in_image_dir();
                    if ( 0 < count($images) ) {
                        $newest_name = $this->get_newest_image_name($images);

                        $current = "{$this->images_url}/{$newest_name}";

                        if ( $this->do_purge_images ) {
                            $this->purge_older_images($images, $newest_name);
                        }
                    }
                } catch ( \Exception $e ) {
                    $this->append_exception( $e );

                    $code = $e->getCode();
                    $message = $e->getMessage();
                    $trace = $e->getTraceAsString();
                    $line = $e->getLine();
                    $file = $e->getFile();
                    $err = new \WP_Error( "Error: {$file}(line {$line}): {$code} {$message}", $trace );

                    # You can hook into this to log errors somewhere if wanted
                    do_action('_privateer_do_web_cam_updates_error', $err);
                }
                return $current;
            }

            protected function append_exception( \Exception $e ) {
                $code = $e->getCode();
                $message = $e->getMessage();
                $trace = $e->getTraceAsString();
                $line = $e->getLine();
                $file = $e->getFile();

                $this->errors[] = new \WP_Error( "Error: {$file}(line {$line}): {$code} {$message}", $trace );
            }

            protected function purge_older_images( $images, $newest_image ) {
                foreach ( $images  as $file_name => $to_remove ) {
                    if ( "{$file_name}" !== "{$newest_image}" ) {
                        if( is_file( "{$to_remove}" ) && is_writeable( "{$to_remove}" ) ) {
                            if ( $this->is_debug ) {
                                $this->notices[] = "Would now be removing {$to_remove}";
                            } else {
                                $removed = unlink( "{$to_remove}" );
                                if ( !$removed ) {
                                    $this->notices[] = "Failed to remove image: {$to_remove}";
                                }
                            }
                        }
                    }
                }
            }

            # Use the _privateer_web_cam_loading filter to get the script to load where wanted
            public function do_setup_javascript() {
                $do_js = apply_filters('_privateer_web_cam_loading', false, $this->image_tag_id);
                if ( $do_js ) {
                    add_action('get_header', array($this, 'do_register_js') );
                    add_action('wp_head', array($this, 'do_enqueue_js'));
                }
            }
            public function do_register_js() {
                wp_register_script('privateer_web_cam', plugins_url( '/js/web_cam_checker.js', __FILE__ ), array( 'jquery', 'heartbeat' ), "0.0.2", true);
            }

            public function do_enqueue_js() {

                $web_cam_config = array(
                    'image_id' => "{$this->image_tag_id}",
                    'refresh_interval' => (int)$this->refresh_interval_s,
                    'debug' => (int) $this->is_debug,
                    'init_retry_ms' => $this->init_retry_ms,
                    'min_init_retries' => $this->min_init_retries
                );
                wp_localize_script('privateer_web_cam', 'pri_web_cam_settings', $web_cam_config );

                wp_enqueue_script('privateer_web_cam');
            }

            function __destruct() {
                do_action('_privateer_web_cam_runtime_errors', $this->errors);
                do_action('_privateer_web_cam_runtime_notices', $this->notices);
            }
        }

        function do_choose_privateer_web_cam_where_to_load($load, $image_id) {
            if ( 'main_cam_image' == "{$image_id}" && is_front_page() ) {
                $load = true;
            }
            return $load;
        }
        add_filter( '_privateer_web_cam_loading', '\Privateer\WebCam\do_choose_privateer_web_cam_where_to_load', 10, 2);

        # Create up an object to handle the web cam and provide wanted defaults
        # Do this multiple times if you will be using different cam directories and/or image tags
        $o_privateer_web_cam = new Privateer_Do_Web_Cam_Updates(
            'main_cam_image', '', '', 0, 0, 0, true, false
        );
        if ( is_a( $o_privateer_web_cam, '\Privateer\WebCam\Privateer_Do_Web_Cam_Updates' ) ) {
            # Set up the ajax responses
            add_filter( 'heartbeat_received', array($o_privateer_web_cam, 'do_process_heartbeat_received'), 10, 3 );
            add_filter( 'heartbeat_nopriv_received', array($o_privateer_web_cam, 'do_process_heartbeat_received'), 10, 3 );

            # Set up the javascript for the front end on templates that you want it used on
            if ( !is_admin() ) {
                add_action( 'get_header', array($o_privateer_web_cam, 'do_setup_javascript'), 9 );
            }
        } else {
            throw new \Exception('Failed to create Privateer_Do_Web_Cam_Updates object', 10001);
        }
    }
} catch ( \Exception $e ) {
    $code = $e->getCode();
    $message = $e->getMessage();
    $trace = $e->getTraceAsString();
    $line = $e->getLine();
    $file = $e->getFile();
    $err = new \WP_Error( "Error: {$file}(line {$line}): {$code} {$message}", $trace );
    do_action('_privateer_web_cam_init_errors', $err);

    if ( WP_DEBUG ) {
        wp_die( "Error in {$file} on line {$line}: Code:{$code}, Message: {$message}, Trace: {$trace}" );
    }
}

El constructor Parámetros:

  • image_tag_id: el valor en el atributo id de la imagen que se mostrará a través de
  • images_dir: ruta completa al directorio en el servidor que contiene las imágenes para esta cámara
  • images_url: las imágenes de URL accesibles en todo el mundo se pueden ver en
  • refresh_interval_s: Segundos entre latidos para esta cámara
  • init_retry_ms: durante init, cuántos milisegundos hay que esperar entre reintentos
  • min_init_retries: el mínimo intenta realizarse en la inicialización antes de abandonar
  • is_debug: si mostrar o no mensajes de depuración en la consola js
  • do_purge_images: si se eliminan o no las imágenes antiguas en el directorio images_dir

Nota: images_dir y images_url suponen que creó una cámara de imágenes en el directorio raíz del complemento. Sin embargo, puedes configurarlos para lo que quieras.

Elegir qué páginas necesitan el javascript de la cámara

Elegí usarlo por defecto para no cargar el javascript en absoluto y usar un filtro para permitir al usuario elegir qué páginas necesitan el script.

function do_choose_privateer_web_cam_where_to_load($load, $image_id) {
    if ( 'main_cam_image' == "{$image_id}" && is_front_page() ) {
        $load = true;
    } else if ( 'second_cam_image' == "{$image_id}" && is_page('cam_two') ) {
        $load = true;
    }
    return $load;
}
add_filter( '_privateer_web_cam_loading', '\Privateer\WebCam\do_choose_privateer_web_cam_where_to_load', 10, 2);

Observe que cambié este para suponer que se están cargando dos objetos de cámara:

  1. Uno que tiene id="main_cam_image" en la página principal
  2. Otro tiene id="second_cam_image" en una página con el slug 'cam_two'

Ajustar como se desee con las diversas funciones is_ * dentro de wordpress para que el archivo javscript se cargue donde se desee.

Aparte de eso, la configuración principal lo sigue inmediatamente.

Si desea crear un objeto de cámara en otro lugar (por ejemplo, un archivo functions.php), querrá asegurarse de usar el espacio de nombres (como el siguiente):

$o_cam_two = new \Privateer\WebCam\Privateer_Do_Web_Cam_Updates(
    'cam_two', '', '', 0, 0, 0, true, false
);
if ( is_a( $o_cam_two, '\Privateer\WebCam\Privateer_Do_Web_Cam_Updates' ) ) {
    # Set up the ajax responses
    add_filter( 'heartbeat_received', array($o_cam_two, 'do_process_heartbeat_received'), 10, 3 );
    add_filter( 'heartbeat_nopriv_received', array($o_cam_two, 'do_process_heartbeat_received'), 10, 3 );

    # Set up the javascript for the front end on templates that you want it used on
    if ( !is_admin() ) {
        add_action( 'get_header', array($o_cam_two, 'do_setup_javascript'), 9 );
    }
}

Sección dos: el archivo Javascript Cualquier consejo sería muy apreciado por aquellos que están más familiarizados con javascript. Solo lo estoy aprendiendo, pero hice lo mejor que pude. Funciona ... y eso es algo.

jQuery(document).ready(function($) {

    (function( document, config ) {

        var settings = {
            $cam_image: null,
            cam_data: {
                current_image_src: null
            },
            image_id: config.image_id,
            debug: parseInt( config.debug ),
            document: document,
            tick_interval: parseInt( config.refresh_interval ),
            waited: 0,
            max_wait: parseInt( config.init_retry_ms ),
            wait_delay_s: parseInt( config.min_init_retries )
        };

        function do_trigger(type, caller, problem ) {
            console.log('Triggering ' + type + ', Caller: ' + caller + ', Problem: ' + problem);
            if ( 'warning' === type ) {
                settings.$document.trigger('web-cam-warning', caller + ': ' + problem);
            } else {
                settings.$document.trigger('web-cam-error', caller + ': ' + problem);
            }
        }

        function do_enqueue_image(data) {
            console.log('Trying to enqueue image...');
            if ( ! wp.heartbeat.enqueue('web_cam_checker_' + settings.image_id, data, true ) ) {
                do_trigger('error', 'do_enqueue_image', 'Failed to add to wp.heartbeat.enqueue. Data: ' + JSON.stringify( data ));
            } else if ( settings.debug ) {
                console.log( 'Queued: ' + JSON.stringify( wp.heartbeat.getQueuedItem('web_cam_checker_' + settings.image_id) ) );
            }
        }

        function do_process_response(el, data) {
            if ( settings.debug ) {
                console.log( 'process_response:' );
                console.log( '######\n ' + 'el: ' + JSON.stringify(el) + '\n######' );
                console.log( '######\n ' + 'data: ' + JSON.stringify(el) + '\n######' );
            }
            if ( data['webcam_new_uri'] ) {
                if ( settings.debug ) {
                    console.log('Found webcam_new_uri: ' + data['webcam_new_uri']);
                }
                settings.cam_data.current_image_src = data['webcam_new_uri'] + '';
                settings.$cam_image.prop('src', settings.cam_data.current_image_src);
                var worked = do_swap_current_image();
                if ( worked ) {
                    if ( settings.debug ) {
                        console.log( 'Swam image worked, setting up next heartbeat queue.' );
                    }
                    do_enqueue_image(settings.cam_data);
                }
            } else {
                if ( data['notice'] ) {
                    if ( settings.debug ) {
                        console.log('Notice Received: ' + data['notice'] + '\nSetting up next heartbeat queue.');
                    }
                    do_enqueue_image(settings.cam_data);
                } else if ( data['error'] ) {
                    do_trigger('error', 'do_process_response', data['error']);
                }
                if ( settings.debug ) {
                    console.log('Full Data: ' + JSON.stringify(data) );
                }
            }
        }

        function do_swap_current_image() {
            var worked = false;

            if ( settings.debug ) {
                console.log('attempting image swap');
            }
            var updated_src = settings.cam_data.current_image_src;
            $("<img/>")
                .one('load', function() {
                    if ( settings.debug ) {
                        console.log('Finished updating to ' + $(this).prop('src'));
                    }
                    worked = true;
                })
                .prop('src', updated_src )
                .each(function(){
                    if ( this.complete ) {
                        $(this).trigger('load');
                    } else {
                        //do_trigger('error', 'do_swap_current_image', 'Did not finish updating to ' + $(this).prop('src'));
                        worked = true
                    }
                });

            return worked;
        }

        function do_setup_timeout( waiting_on ) {
            settings.waited += 1;
            if ( settings.waited < settings.max_wait ) {
                setTimeout( do_init(), settings.wait_delay_s * 1000 );
            } else {
                do_trigger('error', 'do_setup_timeout', 'Giving up on ' + waiting_on + ' (waited ' + settings.waited + ' times)');
            }
        }

        function do_init() {
            if ( typeof window.wp === 'undefined' ) {
                do_setup_timeout('window.wp');
            } else if ( typeof window.wp.heartbeat === 'undefined' ) {
                do_setup_timeout('window.wp.heartbeat');
            } else if ( typeof settings.image_id === 'undefined' ) {
                do_trigger('error', 'do_init', 'Cannot start web cam without html image tag id name');
            } else {
                settings.$cam_image = $('#' + settings.image_id);
                console.log('Settings:' + JSON.stringify(settings.$cam_image));
                if ( 0 === settings.$cam_image.length ) {
                    do_trigger('error', 'do_init', 'Failed to locate image #' + settings.image_id);
                } else {
                    if ( settings.interval < 5 ) {
                        do_trigger('warning', 'do_init', 'Interval cannot be shorter than 5 seconds. Detected as ' + settings.interval );
                        settings.interval = 5;
                    } else if ( settings.interval > 120 ) {
                        do_trigger('warning', 'do_init', 'Interval cannot be longer that 120 seconds. Detected as ' + settings.interval );
                        settings.interval = 120;
                    }
                    settings.cam_data.current_image_src = settings.$cam_image.prop('src');
                    console.log('Settings Now: ' + JSON.stringify( settings ));
                    do_enqueue_image( settings.cam_data );
                    document.on('heartbeat-send', function(el, data) {
                        if ( settings.debug ) {
                            console.log('Data sent was ' + JSON.stringify( data ));
                        }
                    }).on('heartbeat-tick.web_cam_checker_' + settings.image_id, function(el, data) {
                        console.log('detected heartbeat tick:' + JSON.stringify(el));
                        if ( data.hasOwnProperty('web_cam_checker_' + settings.image_id) ) {
                            if ( settings.debug ) {
                                console.log('Data has web_cam_checker_' + settings.image_id);
                            }
                            do_process_response(el, data['web_cam_checker_' + settings.image_id]);
                        } else if ( settings.debug ) {
                            console.log('Data lacks web_cam_checker_' + settings.image_id + ': ' + JSON.stringify(data) );
                        }
                    });
                    wp.heartbeat.interval(settings.tick_interval);
                }
            }
        }

        do_init();

    })( $(document), pri_web_cam_settings );

    $(document)
        .on('web-cam-error', function(e) {
            console.log('Web Cam Error: ' + e);
        })
        .on('web-cam-warning', function(e) {
            console.log('Web Cam Warning: ' + e);
        })
        .on('heartbeat.error', function(e) {
            console.log('Heartbeat Error: ' + JSON.stringify(e) );
        });

});

Gracias por la idea Kaiser. No había oído hablar de la API de latidos del corazón y estaba buscando algo para probar al aumentar el conocimiento de javascript en ... así que este fue un buen ejercicio.

Solo lo he probado en un servidor LAMP que navega desde Firefox ... y no, todavía no he hecho una declaración estricta ... pero podría hacerlo la próxima vez que tenga algo de tiempo.

De todos modos, espero que esto ayude a alguien un poco.

Cualquiera para los nuevos en el código ...

Para que esto funcione como está:

  1. Cree un directorio en su directorio wp-content / plugins (nombre que sea, usaré privateer-web-cam-updates)
  2. Cree un nuevo archivo de texto llamado privateer-web-cam-updates.php en ese directorio y ábralo en un editor de texto simple
  3. Agregue un id="main_cam_image" a una etiqueta de imagen en su sitio (diga su página principal). Podría parecer <img src="#" id="main_cam_image" title="My Web Cam" />
  4. Edite la siguiente sección de código en el complemento.

function do_choose_privateer_web_cam_where_to_load($load, $image_id) { if ( 'main_cam_image' == "{$image_id}" && is_front_page() ) { $load = true; } return $load; } add_filter( '_privateer_web_cam_loading', '\Privateer\WebCam\do_choose_privateer_web_cam_where_to_load', 10, 2);

Si tienes tu imagen en un blog, cambia de is_front_page() a is_home() . Si lo tiene en una página, obtenga el ID de la página y luego cambie is_front_page() a is_page(n) donde n es el ID de la página.

  1. Crea un subdirectorio de imágenes de cámara en la carpeta de complementos y configura tu cámara web para colocar las imágenes allí.
  2. Cree un subdirectorio js en la carpeta de complementos.
  3. Copie el código de la Sección 2 en un nuevo archivo de texto llamado web_cam_checker.js

Y, nuevamente, la crítica constructiva siempre es apreciada. (Parece que no se puede detectar la última sección como código por algún motivo)

    
respondido por el Privateer 19.01.2015 - 06:26

Lea otras preguntas en las etiquetas