Files
libgphoto2/camlibs/quicktake1x0/quicktake1x0.c
Colin Leroy-Mira 9a07ec3adb Add support for QuickTake 100/150 (#936)
* Add support for QuickTake 100/150

- Summary
- Get/Set configuration
- Get preview
- Get PPM files
- Get raw files (QTK format)

Serial protocol reference: https://www.colino.net/wordpress/en/archives/2023/10/29/the-apple-quicktake-100-serial-communication-protocol/
QTKT / QTKN decoding algorithms from dcraw (GPL licensed).

* Fix theorical integer overflows

* Fix license after careful re-reading of dcraw's license

* Fix quality mode index

* Fix Quicktake 150 thumbnails

* Fix unused variables and document thumbnail formats
2024-01-07 11:18:19 +01:00

928 lines
26 KiB
C

/* quicktake1x0.c
*
* Copyright 2023, Colin Leroy-Mira <colin@colino.net>
* Quicktake 100/150 protocol documentation available at
* https://www.colino.net/wordpress/en/archives/2023/10/29/the-apple-quicktake-100-serial-communication-protocol/
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "libgphoto2/i18n.h"
#include "quicktake1x0.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <gphoto2/gphoto2-library.h>
#define GP_MODULE "Quicktake 1x0"
#define BUFFER_SIZE 1024
static unsigned char buffer[BUFFER_SIZE];
#define CHECK_RESULT(result) {int r = result; if (r < 0) return (r);}
/* Get an ack from the camera
* Acks are single-byte, 0x00 in case of success, 0x02 in case of error
*/
static int
get_ack(GPPort *port)
{
if (gp_port_read(port, (char *)buffer, 1) < 1)
return GP_ERROR_IO;
return buffer[0] == 0x00 ? GP_OK : GP_ERROR_IO;
}
/* Send an ack to the camera
* Our acks are single byte, 0x06 that mean "I'm ready to get data"
*/
static int
send_ack(GPPort *port)
{
char cmd[] = {0x06};
return gp_port_write (port, cmd, 1) == 1 ? GP_OK : GP_ERROR_IO;
}
/* Send a command to the camera
* cmd: the command as an hexadecimal char array
* len: the length of the array
* s_ack: whether the camera expects us to send an ack after we receive
* its own ack
*/
static int
send_command(GPPort *port, const char *cmd, int len, int s_ack)
{
if (gp_port_write (port, cmd, len) < GP_OK)
return GP_ERROR_IO;
if (get_ack(port) != GP_OK)
return GP_ERROR_IO;
if (s_ack)
return send_ack(port);
return GP_OK;
}
/* Ping the camera
* A simple command that does nothing, and that the camera replies to
* with an ack. Useful to make sure the camera did not go to sleep.
*/
static int
qt1x0_send_ping(GPPort *port)
{
char cmd[] = {0x16,0x00,0x00,0x00,0x00,0x00,0x00};
return send_command(port, cmd, sizeof cmd, 0);
}
/* Initialize the camera */
static int
qt1x0_initialize (Camera *camera)
{
/* "Hello" command, sent at 9600bps, 8n1. At bytes 6-7, the desired serial
* speed is indicated. the last byte is a checksum:
* (sum of the preceding bytes)&0xFF
*/
char hello[] = {0x5A,0xA5,0x55,0x05,0x00,0x00,0xE1,0x00,0x00,0x80,0x02,0x00,0xBC};
/* Command that triggers the serial speed upgrade */
char upgrade_speed[] = {0x16,0x2A,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x03,0x03,0x30,0x04,0x00};
GPPortSettings settings;
/* Clearing DTR wakes the camera up. */
CHECK_RESULT(gp_port_set_pin (camera->port, GP_PIN_DTR, GP_LEVEL_LOW));
usleep(100 * 1000);
CHECK_RESULT(gp_port_set_pin (camera->port, GP_PIN_DTR, GP_LEVEL_HIGH));
/* It then tells us 7 bytes that help identify it */
if (gp_port_read (camera->port, (char *)buffer, 7) < 7)
return GP_ERROR_MODEL_NOT_FOUND;
if ((unsigned char)buffer[0] != 0xA5)
return GP_ERROR_MODEL_NOT_FOUND;
if ((unsigned char)buffer[3] == 0xC8)
camera->pl->model = QUICKTAKE_MODEL_150;
else
camera->pl->model = QUICKTAKE_MODEL_100;
/* We then send our first command to inform it of the speed we desire */
if (gp_port_write (camera->port, hello, sizeof(hello)) < GP_OK)
return GP_ERROR_MODEL_NOT_FOUND;
/* The camera replies, with data we can ignore */
if (gp_port_read (camera->port, (char *)buffer, 10) < 10)
return GP_ERROR_MODEL_NOT_FOUND;
/* At this point, the camera expects us to set even parity. */
CHECK_RESULT (gp_port_get_settings (camera->port, &settings));
settings.serial.parity = GP_PORT_SERIAL_PARITY_EVEN;
CHECK_RESULT (gp_port_set_settings (camera->port, settings));
/* We let it do the same. */
usleep(1 * 1000 * 1000);
/* Once parity is set on both sides, we ask to upgrade speed. */
if (gp_port_write (camera->port, upgrade_speed, sizeof(upgrade_speed)) < GP_OK)
return GP_ERROR_MODEL_NOT_FOUND;
/* The camera acks that, */
if (get_ack(camera->port) != GP_OK)
return GP_ERROR_MODEL_NOT_FOUND;
/* We ack the ack, */
if (send_ack(camera->port) != GP_OK)
return GP_ERROR_MODEL_NOT_FOUND;
/* Wait a little bit of time, */
usleep(100 * 1000);
/* Upgrade our serial line, */
settings.serial.speed = 57600;
CHECK_RESULT (gp_port_set_settings (camera->port, settings));
/* And flush a full kilobytes of 0xAA that the camera sends us,
* probably to verify that we're good to go.
*/
gp_port_read (camera->port, (char *)buffer, 1024); /* Ignored */
/* We ack that, */
if (send_ack(camera->port) != GP_OK)
return GP_ERROR_MODEL_NOT_FOUND;
/* It acks our ack, and we're ready! */
return get_ack(camera->port);
}
/* Get information from the camera. It tells us how many pics were taken,
* how much free space there is, its current battery level, quality mode,
* flash mode, and date and time.
*/
static int
camera_get_info (Camera *camera)
{
/* Command to get information from the camera.
* byte 3 is the requested information:
* - 0x00 for a thumbnail
* - 0x10 for a full picture
* - 0x21 for metadata
* - 0x30 for camera information
* byte 6 is the picture number. (0 for camera information)
* bytes 7 to 9 is the expected response size:
* - 0x000040 for metadata
* - 0x000080 for camera information
* - 0x000960 for the thumbnail
* - depends on the picture for the full picture. We get the full
* picture size in the metadata response so we can reuse that.
*/
char cmd[] = {0x16,0x28,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x80,0x00};
/* Offsets of interesting data */
#define BATTERY_IDX 0x02
#define NUM_PICS_IDX 0x04
#define LEFT_PICS_IDX 0x06
#define MONTH_IDX 0x10
#define DAY_IDX 0x11
#define YEAR_IDX 0x12
#define HOUR_IDX 0x13
#define MIN_IDX 0x14
#define SEC_IDX 0x15
#define FLASH_IDX 0x16
#define QUALITY_IDX 0x1B
#define NAME_IDX 0x2F
/* Ping it */
if (qt1x0_send_ping(camera->port) != GP_OK)
return GP_ERROR_IO;
/* Send our command */
if (send_command(camera->port, cmd, sizeof cmd, 1) != GP_OK)
return GP_ERROR_IO;
/* Read the 128 bytes response */
if (gp_port_read(camera->port, (char *)buffer, 128) != 128)
return GP_ERROR_IO;
/* Copy the relevant data. */
strcpy(camera->pl->name, (char *)buffer + NAME_IDX);
while (camera->pl->name[strlen(camera->pl->name) - 1] == ' ')
camera->pl->name[strlen(camera->pl->name) - 1] = '\0';
camera->pl->num_pics = buffer[NUM_PICS_IDX];
camera->pl->free_pics = buffer[LEFT_PICS_IDX];
camera->pl->battery_level = buffer[BATTERY_IDX];
camera->pl->quality_mode = buffer[QUALITY_IDX];
camera->pl->flash_mode = buffer[FLASH_IDX];
camera->pl->year = buffer[YEAR_IDX] + 2000;
camera->pl->month = buffer[MONTH_IDX];
camera->pl->day = buffer[DAY_IDX];
camera->pl->hour = buffer[HOUR_IDX];
camera->pl->minute = buffer[MIN_IDX];
camera->pl->info_fetched = 1;
return GP_OK;
}
/* Change the name of the camera */
static int
camera_set_name(Camera *camera, const char *name)
{
char set_name_cmd[] = {0x16,0x2a,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x22,0x00,0x02,0x20,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20};
#define SET_NAME_IDX 0x0D
int len;
len = strlen(name);
if (len > 31)
len = 31;
if (qt1x0_send_ping(camera->port) != GP_OK)
return GP_ERROR_IO;
memcpy(set_name_cmd + SET_NAME_IDX, name, len);
return send_command(camera->port, set_name_cmd, sizeof set_name_cmd, 0);
}
/* Change the date of the camera */
static int
camera_set_time(Camera *camera, int year, int month, int day, int hour, int minute)
{
char set_time_cmd[] = {0x16,0x2A,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x01,0x06,0x00,0x00,0x00,0x00,0x00,0x00};
#define SET_MONTH_IDX 0x0D
#define SET_DAY_IDX 0x0E
#define SET_YEAR_IDX 0x0F
#define SET_HOUR_IDX 0x10
#define SET_MIN_IDX 0x11
#define SET_SEC_IDX 0x12
if (qt1x0_send_ping(camera->port) != GP_OK)
return GP_ERROR_IO;
set_time_cmd[SET_DAY_IDX] = day;
set_time_cmd[SET_MONTH_IDX] = month;
set_time_cmd[SET_YEAR_IDX] = year % 100;
set_time_cmd[SET_HOUR_IDX] = hour;
set_time_cmd[SET_MIN_IDX] = minute;
set_time_cmd[SET_SEC_IDX] = 0;
return send_command(camera->port, set_time_cmd, sizeof set_time_cmd, 0);
}
/* Change the flash mode of the camera */
static int
camera_set_flash_mode(Camera *camera, Quicktake1x0FlashMode flash_mode)
{
char set_flash_cmd[] = {0x16,0x2A,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x07,0x01,0x00};
#define SET_FLASH_IDX 0x0D
set_flash_cmd[SET_FLASH_IDX] = flash_mode;
return send_command(camera->port, set_flash_cmd, sizeof set_flash_cmd, 0);
}
/* Change the quality_mode of the camera */
static int
camera_set_quality_mode(Camera *camera, QuickTake1x0Quality quality_mode)
{
char set_quality_cmd[] = {0x16,0x2A,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x06,0x02,0x10,0x00};
#define SET_QUALITY_IDX 0x0D
set_quality_cmd[SET_QUALITY_IDX] = quality_mode == QUALITY_HIGH ? 0x10 : 0x20;
return send_command(camera->port, set_quality_cmd, sizeof set_quality_cmd, 0);
}
static time_t
camera_get_time(Camera *camera)
{
struct tm tm_time;
/* Build a UNIX representation of the picture creation time */
tm_time.tm_year = camera->pl->year - 1900;
tm_time.tm_mon = camera->pl->month - 1;
tm_time.tm_mday = camera->pl->day;
tm_time.tm_hour = camera->pl->hour;
tm_time.tm_min = camera->pl->minute;
tm_time.tm_sec = 0;
tm_time.tm_isdst = -1;
return mktime(&tm_time);
}
static int
camera_trigger_capture (Camera *camera, GPContext *context)
{
char cmd[] = {0x16,0x1B,0x00,0x00,0x00,0x00,0x00};
/* Ping, */
if (qt1x0_send_ping(camera->port) != GP_OK)
return GP_ERROR_IO;
/* Command, */
if (send_command(camera->port, cmd, sizeof cmd, 0) != GP_OK)
return GP_ERROR_IO;
/* And update our information to keep track of the
* taken/free pics counters
*/
return camera_get_info(camera);
}
/* Command the camera to capture an image. */
static int
camera_capture (Camera *camera, CameraCaptureType type, CameraFilePath *path,
GPContext *context)
{
if (type != GP_CAPTURE_IMAGE)
return (GP_ERROR_NOT_SUPPORTED);
if (camera_trigger_capture(camera, context) != GP_OK)
return GP_ERROR_IO;
strcpy (path->folder, "/");
sprintf (path->name, "Image_%02i.ppm", camera->pl->num_pics);
CHECK_RESULT (gp_filesystem_append (camera->fs, "/", path->name, context));
return (GP_OK);
}
int
camera_abilities (CameraAbilitiesList *list)
{
CameraAbilities a;
memset (&a, 0, sizeof(a));
strcpy (a.model, "Apple QuickTake 1x0");
a.status = GP_DRIVER_STATUS_PRODUCTION;
a.port = GP_PORT_SERIAL;
a.speed[0] = 57600;
a.speed[1] = 0;
a.operations = GP_OPERATION_CAPTURE_IMAGE|GP_OPERATION_CONFIG;
a.file_operations = GP_FILE_OPERATION_RAW|GP_FILE_OPERATION_PREVIEW;
a.folder_operations = GP_FOLDER_OPERATION_DELETE_ALL;
CHECK_RESULT (gp_abilities_list_append (list, a));
return GP_OK;
}
static int
camera_about (Camera *camera, CameraText *about, GPContext *context)
{
/* Translators: please write 'M"uller' and 'Mei"sner' (that
is, with u-umlaut and eszett resp.) if your charset
allows it. If not, use "Mueller" and "Meissner". */
strcpy (about->text, _("The Apple QuickTake 1x0 driver has been written "
"by Colin Leroy-Mira <colin@colino.net>.\n"
"It handles the QuickTake 100, 100 plus and 150 models.\n"
"\n"
"It can fetch information, thumbnails, raw and ppm data, and "
"command the camera to take pictures.")
);
return GP_OK;
}
static const char *
model_to_str(int model)
{
switch(model) {
case QUICKTAKE_MODEL_100:
return "QuickTake 100";
case QUICKTAKE_MODEL_150:
return "QuickTake 150";
default:
return "unknown";
}
}
static const char *
quality_to_str(int mode)
{
switch(mode) {
case QUALITY_STANDARD:
return _("Standard");
case QUALITY_HIGH:
return _("High");
default:
return _("Unknown");
}
}
static const char *
flash_to_str(int mode)
{
switch(mode) {
case FLASH_AUTO:
return _("Automatic");
case FLASH_OFF:
return _("Disabled");
case FLASH_ON:
return _("Forced");
default:
return _("Unknown");
}
}
/* Show the information we gathered */
static int
camera_summary (Camera *camera, CameraText *summary, GPContext *context)
{
if (!camera->pl->info_fetched &&
camera_get_info(camera) != GP_OK)
return GP_ERROR_IO;
sprintf(summary->text,
"Camera model: %s\n"
"Camera name: %s\n"
"Pictures taken: %d\n"
"Available space: %d\n"
"\n"
"Battery level: %d%%\n"
"Current quality mode: %s\n"
"Current flash mode: %s\n"
"Camera date and time: %04d/%02d/%02d %02d:%02d\n",
model_to_str(camera->pl->model),
camera->pl->name,
camera->pl->num_pics,
camera->pl->free_pics,
camera->pl->battery_level,
quality_to_str(camera->pl->quality_mode),
flash_to_str(camera->pl->flash_mode),
camera->pl->year,
camera->pl->month,
camera->pl->day,
camera->pl->hour,
camera->pl->minute);
return GP_OK;
}
/* Get the list of files on the camera, that is, images 1 to N */
static int
file_list_func (CameraFilesystem *fs, const char *folder, CameraList *list,
void *data, GPContext *context)
{
Camera *camera = data;
if (!camera->pl->info_fetched &&
camera_get_info(camera) != GP_OK)
return GP_ERROR_IO;
return gp_list_populate (list, "Image_%02i.ppm", camera->pl->num_pics);
}
#define BLOCK_SIZE 512
#define WH_OFFSET 0x220
#define DATA_OFFSET 0x2E0
/* Camera "metadata" command. Elicits a 64-byte response containing
* metadata about the picture N: width, height, size, creation time,
* quality mode, flash mode.
*/
static int
get_metadata (GPPort *port, int n, unsigned char *data)
{
/* Command to get information from the camera (see camera_get_info
* for value details).
*/
char cmd[] = {0x16,0x28,0x00,0x21,0x00,0x00,0x01,0x00,0x00,0x40,0x00};
#define PNUM_IDX 0x06
#define PSIZE_IDX 0x07
#define IMG_NUM_IDX 0x03
#define IMG_SIZE_IDX 0x05
#define IMG_WIDTH_IDX 0x08
#define IMG_HEIGHT_IDX 0x0A
#define IMG_MONTH_IDX 0x0D
#define IMG_DAY_IDX 0x0E
#define IMG_YEAR_IDX 0x0F
#define IMG_HOUR_IDX 0x10
#define IMG_MINUTE_IDX 0x11
#define IMG_SECOND_IDX 0x12
#define IMG_FLASH_IDX 0x13
#define IMG_QUALITY_IDX 0x18
cmd[PNUM_IDX] = n;
if (qt1x0_send_ping(port) != GP_OK)
return GP_ERROR_IO;
if (send_command(port, cmd, sizeof cmd, 1) != GP_OK)
return GP_ERROR_IO;
if (gp_port_read(port, (char *)data, 64) != 64)
return GP_ERROR_IO;
return (GP_OK);
}
static int
width_from_metadata(const unsigned char *buffer)
{
return (buffer[IMG_WIDTH_IDX + 1]) + (buffer[IMG_WIDTH_IDX] << 8);
}
static int
height_from_metadata(const unsigned char *buffer)
{
return (buffer[IMG_HEIGHT_IDX + 1]) + (buffer[IMG_HEIGHT_IDX] << 8);
}
static int
size_from_metadata(const unsigned char *buffer)
{
return (buffer[IMG_SIZE_IDX] << 16)
+ (buffer[IMG_SIZE_IDX + 1] << 8)
+ (buffer[IMG_SIZE_IDX + 2]);
}
static time_t
mtime_from_metadata(const unsigned char *buffer)
{
struct tm tm_time;
/* Build a UNIX representation of the picture creation time */
tm_time.tm_year = buffer[IMG_YEAR_IDX] + 100;
tm_time.tm_mon = buffer[IMG_MONTH_IDX] - 1;
tm_time.tm_mday = buffer[IMG_DAY_IDX];
tm_time.tm_hour = buffer[IMG_HOUR_IDX];
tm_time.tm_min = buffer[IMG_MINUTE_IDX];
tm_time.tm_sec = buffer[IMG_SECOND_IDX];
tm_time.tm_isdst = 0;
return mktime(&tm_time);
}
static int
get_info_func (CameraFilesystem *fs, const char *folder, const char *file,
CameraFileInfo *info, void *data, GPContext *context)
{
Camera *camera = data;
int n;
CHECK_RESULT (n = gp_filesystem_number (fs, folder, file, context));
/* The camera counts from 1. */
n++;
if (get_metadata(camera->port, n, buffer) != GP_OK)
return GP_ERROR_IO;
/* Build the file information */
info->file.fields = GP_FILE_INFO_SIZE | GP_FILE_INFO_WIDTH |
GP_FILE_INFO_HEIGHT | GP_FILE_INFO_TYPE |
GP_FILE_INFO_MTIME;
info->file.width = width_from_metadata(buffer);
info->file.height = height_from_metadata(buffer);
info->file.mtime = mtime_from_metadata(buffer);
info->file.size = qtk_ppm_size(info->file.width, info->file.height);
strcpy (info->file.type, GP_MIME_PPM);
/* Build the preview information */
info->preview.fields = GP_FILE_INFO_SIZE | GP_FILE_INFO_WIDTH |
GP_FILE_INFO_HEIGHT | GP_FILE_INFO_TYPE;
info->preview.width = QT1X0_THUMB_WIDTH;
info->preview.height = QT1X0_THUMB_HEIGHT;
info->preview.size = qtk_ppm_size(QT1X0_THUMB_WIDTH, QT1X0_THUMB_HEIGHT);
strcpy (info->preview.type, GP_MIME_PPM);
return (GP_OK);
}
/* Read (long) data from the camera. Wraps around gp_port_read, as the
* camera expects us to tell it to continue every 512 bytes.
*/
static int
receive_data(GPPort *port, unsigned char *data, int size)
{
int i;
for (i = 0; i < (size / BLOCK_SIZE); i++) {
/* Read, */
if (gp_port_read(port, (char *)data, BLOCK_SIZE) != BLOCK_SIZE)
return GP_ERROR_IO;
data += BLOCK_SIZE;
/* ack, */
send_ack(port);
}
/* and read the last partial block */
size %= BLOCK_SIZE;
if (gp_port_read(port, (char *)data, size) != size)
return GP_ERROR_IO;
return GP_OK;
}
static int
get_file_func (CameraFilesystem *fs, const char *folder, const char *filename,
CameraFileType type, CameraFile *file, void *user_data,
GPContext *context)
{
/* Commands to get information from the camera (see camera_get_info
* for value details).
*/
char full_photo_cmd[] = {0x16,0x28,0x00,0x10,0x00,0x00,0x01,0x00,0x70,0x80,0x00};
char thumbnail_cmd[] = {0x16,0x28,0x00,0x00,0x00,0x00,0x01,0x00,0x09,0x60,0x00};
Camera *camera = user_data;
int n, raw_size, file_size, width = 0, height = 0;
unsigned char *data;
char *cmd;
const char *mime_type;
time_t mtime;
CHECK_RESULT (n = gp_filesystem_number (camera->fs, folder, filename, context));
/* The camera counts from 1 */
n++;
/* Get metadata */
if (get_metadata(camera->port, n, buffer) != GP_OK)
return GP_ERROR_IO;
mtime = mtime_from_metadata(buffer);
/* Set parameters according to the user's request */
if (type == GP_FILE_TYPE_PREVIEW) {
cmd = thumbnail_cmd;
mime_type = GP_MIME_PPM;
raw_size = QT1X0_THUMB_SIZE;
file_size = qtk_ppm_size(QT1X0_THUMB_WIDTH, QT1X0_THUMB_HEIGHT);
} else if (type == GP_FILE_TYPE_RAW) {
cmd = full_photo_cmd;
mime_type = camera->pl->model == QUICKTAKE_MODEL_100 ? GP_MIME_QTKT : GP_MIME_QTKN;
raw_size = size_from_metadata(buffer);
file_size = raw_size + DATA_OFFSET;
} else if (type == GP_FILE_TYPE_NORMAL) {
cmd = full_photo_cmd;
mime_type = GP_MIME_PPM;
width = width_from_metadata(buffer);
height = height_from_metadata(buffer);
raw_size = size_from_metadata(buffer);
file_size = qtk_ppm_size(width, height);
} else
return GP_ERROR_NOT_SUPPORTED;
/* Set the command's parameters */
cmd[PNUM_IDX] = n;
cmd[PSIZE_IDX] = (raw_size >> 16) & 0xFF;
cmd[PSIZE_IDX + 1] = (raw_size >> 8) & 0xFF;
cmd[PSIZE_IDX + 2] = (raw_size >> 0) & 0xFF;
data = malloc(file_size);
if (data == NULL)
return GP_ERROR_NO_MEMORY;
memset(data, 0, file_size);
/* Send the command to the camera */
if (send_command(camera->port, cmd, sizeof full_photo_cmd, 1) != GP_OK)
return GP_ERROR_IO;
if (type == GP_FILE_TYPE_PREVIEW) {
unsigned char *ppm_data;
/* Get the data */
CHECK_RESULT(receive_data(camera->port, data, raw_size));
/* And decode the thumbnail to PPM */
CHECK_RESULT(qtk_thumbnail_decode(data, &ppm_data, camera->pl->model));
free(data);
data = ppm_data;
} else if (type == GP_FILE_TYPE_RAW) {
/* Build the .qtk file header */
qtk_raw_header(data, camera->pl->model == QUICKTAKE_MODEL_100 ? "qktk":"qktn");
memcpy(data + 0x12, buffer + 4, 60);
data[WH_OFFSET] = buffer[IMG_HEIGHT_IDX];
data[WH_OFFSET + 1] = buffer[IMG_HEIGHT_IDX + 1];
data[WH_OFFSET + 2] = buffer[IMG_WIDTH_IDX];
data[WH_OFFSET + 3] = buffer[IMG_WIDTH_IDX + 1];
/* And get data after the header */
CHECK_RESULT(receive_data(camera->port, data + DATA_OFFSET, raw_size));
} else if (type == GP_FILE_TYPE_NORMAL) {
unsigned char *ppm_data;
/* Get the data */
CHECK_RESULT(receive_data(camera->port, data, raw_size));
/* And decode it to PPM */
if (camera->pl->model == QUICKTAKE_MODEL_100) {
CHECK_RESULT(qtkt_decode(data, width, height, &ppm_data));
} else {
CHECK_RESULT(qtkn_decode(data, width, height, &ppm_data));
}
free(data);
data = ppm_data;
} else
return GP_ERROR_NOT_SUPPORTED;
/* Finalize the file */
CHECK_RESULT (gp_file_set_mtime (file, mtime));
CHECK_RESULT (gp_file_set_mime_type (file, mime_type));
CHECK_RESULT (gp_file_set_data_and_size (file, (char*)data, file_size));
return GP_OK;
}
/* Command to delete all files from the camera */
static int
delete_all_func (CameraFilesystem *fs, const char* folder, void *data,
GPContext *context)
{
char cmd[] = {0x16,0x29,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
Camera *camera = data;
if (strcmp (folder, "/"))
return (GP_ERROR_DIRECTORY_NOT_FOUND);
if (qt1x0_send_ping(camera->port) != GP_OK) {
return GP_ERROR_IO;
}
return send_command(camera->port, cmd, sizeof cmd, 1);
}
static CameraFilesystemFuncs fsfuncs = {
.file_list_func = file_list_func,
.get_info_func = get_info_func,
.get_file_func = get_file_func,
.delete_all_func = delete_all_func,
};
static int
camera_config_get (Camera *camera, CameraWidget **window, GPContext *context)
{
CameraWidget *child;
time_t t;
if (!camera->pl->info_fetched &&
camera_get_info(camera) != GP_OK)
return GP_ERROR_IO;
gp_widget_new (GP_WIDGET_WINDOW, _("Camera Configuration"), window);
gp_widget_new (GP_WIDGET_TEXT, _("Camera name"), &child);
gp_widget_set_name (child, "camera_name");
gp_widget_set_value (child, camera->pl->name);
gp_widget_append (*window, child);
gp_widget_new (GP_WIDGET_RADIO, _("Image quality"), &child);
gp_widget_set_name (child, "quality_mode");
gp_widget_add_choice (child, quality_to_str(QUALITY_STANDARD));
gp_widget_add_choice (child, quality_to_str(QUALITY_HIGH));
gp_widget_set_value (child, quality_to_str(camera->pl->quality_mode));
gp_widget_append (*window, child);
gp_widget_new (GP_WIDGET_RADIO, _("Flash"), &child);
gp_widget_set_name (child, "flash_mode");
gp_widget_add_choice (child, flash_to_str(FLASH_ON));
gp_widget_add_choice (child, flash_to_str(FLASH_OFF));
gp_widget_add_choice (child, flash_to_str(FLASH_AUTO));
gp_widget_set_value (child, flash_to_str(camera->pl->flash_mode));
gp_widget_append (*window, child);
gp_widget_new (GP_WIDGET_DATE, _("Camera date"), &child);
gp_widget_set_name (child, "camera_date");
t = camera_get_time(camera);
gp_widget_set_value (child, &t);
gp_widget_append (*window, child);
return GP_OK;
}
static int
camera_config_set (Camera *camera, CameraWidget *window, GPContext *context)
{
CameraWidget *widget;
const char *val;
CHECK_RESULT(gp_widget_get_child_by_name (window, "camera_name", &widget));
if (gp_widget_changed(widget)) {
CHECK_RESULT(gp_widget_get_value(widget, &val));
CHECK_RESULT(camera_set_name(camera, val));
}
CHECK_RESULT(gp_widget_get_child_by_name (window, "quality_mode", &widget));
if (gp_widget_changed(widget)) {
CHECK_RESULT(gp_widget_get_value(widget, &val));
if (!strcmp(val, quality_to_str(QUALITY_HIGH))) {
CHECK_RESULT(camera_set_quality_mode(camera, QUALITY_HIGH));
} else {
CHECK_RESULT(camera_set_quality_mode(camera, QUALITY_STANDARD));
}
}
CHECK_RESULT(gp_widget_get_child_by_name (window, "flash_mode", &widget));
if (gp_widget_changed(widget)) {
CHECK_RESULT(gp_widget_get_value(widget, &val));
if (!strcmp(val, flash_to_str(FLASH_ON))) {
CHECK_RESULT(camera_set_flash_mode(camera, FLASH_ON));
} else if (!strcmp(val, flash_to_str(FLASH_OFF))) {
CHECK_RESULT(camera_set_flash_mode(camera, FLASH_OFF));
} else {
CHECK_RESULT(camera_set_flash_mode(camera, FLASH_AUTO));
}
}
CHECK_RESULT(gp_widget_get_child_by_name (window, "camera_date", &widget));
if (gp_widget_changed(widget)) {
int v;
time_t time;
struct tm *tm_time;
CHECK_RESULT(gp_widget_get_value(widget, &v));
time = (time_t)v;
tm_time = localtime(&time);
CHECK_RESULT(camera_set_time(camera,
tm_time->tm_year + 1900, tm_time->tm_mon + 1,
tm_time->tm_mday, tm_time->tm_hour, tm_time->tm_min));
}
return camera_get_info(camera);
}
static int
camera_exit (Camera *camera, GPContext *context)
{
if (camera->pl) {
free (camera->pl);
camera->pl = NULL;
}
return (GP_OK);
}
int
camera_id (CameraText *id)
{
strcpy (id->text, "Apple QuickTake 1x0");
return GP_OK;
}
int camera_init (Camera *camera, GPContext *context)
{
GPPortSettings settings;
GP_DEBUG("Init QuickTake 1x0...");
/* First, set up all the function pointers */
camera->functions->about = camera_about;
camera->functions->summary = camera_summary;
camera->functions->capture = camera_capture;
camera->functions->trigger_capture = camera_trigger_capture;
camera->functions->exit = camera_exit;
camera->functions->get_config = camera_config_get;
camera->functions->set_config = camera_config_set;
camera->pl = calloc (1, sizeof (CameraPrivateLibrary));
if (!camera->pl)
return (GP_ERROR_NO_MEMORY);
camera->pl->model = QUICKTAKE_MODEL_UNKNOWN;
/* Now, tell the filesystem where to get lists and info */
gp_filesystem_set_funcs (camera->fs, &fsfuncs, camera);
/* Initial serial settings */
CHECK_RESULT (gp_port_get_settings (camera->port, &settings));
settings.serial.speed = 9600;
settings.serial.bits = 8;
settings.serial.parity = GP_PORT_SERIAL_PARITY_OFF;
settings.serial.stopbits= 1;
CHECK_RESULT (gp_port_set_settings (camera->port, settings));
CHECK_RESULT (gp_port_set_timeout (camera->port, 20000));
/* Open the port and check if the camera is there */
CHECK_RESULT (qt1x0_initialize (camera));
return (GP_OK);
}