V3.0 with servo control and presets

Control servos using hardware PWM GPIOs or other GPIOs using ServoBlaster.

Presets group motion detect counts/limits and motion regions into settings
presets and servo positions into position presets.  New files preset.c
and servo.c

www/state link to /run/pikrellcam/state

Single videos link on index.php

Fix timelapse stills to not create zero sized jpegs.

Motion regions save/load by name usage changed to backup roll for presets.
This commit is contained in:
Bill Wilson 2016-04-23 10:18:38 -05:00
parent 73262d53c0
commit a110be2977
37 changed files with 3577 additions and 646 deletions

View File

@ -100,6 +100,46 @@ slist_prepend(SList *list, void *data)
return new_list;
}
SList *
slist_insert(SList *list, void *data, int position)
{
SList *prev_list,
*tmp_list,
*new_list;
if (position < 0)
return slist_append(list, data);
else if (position == 0)
return slist_prepend(list, data);
new_list = calloc(1, sizeof(SList));
new_list->data = data;
if (!list)
return new_list;
prev_list = NULL;
tmp_list = list;
while ((position-- > 0) && tmp_list)
{
prev_list = tmp_list;
tmp_list = tmp_list->next;
}
if (prev_list)
{
new_list->next = prev_list->next;
prev_list->next = new_list;
}
else
{
new_list->next = list;
list = new_list;
}
return list;
}
SList *
slist_remove(SList *list, void *data)
{
@ -166,6 +206,22 @@ slist_length(SList *list)
return length;
}
int
slist_index(SList *list, void *data)
{
int i;
i = 0;
while (list)
{
if (list->data == data)
return i;
i++;
list = list->next;
}
return -1;
}
SList *
slist_remove_link(SList *list, SList *link)
{
@ -189,3 +245,50 @@ slist_remove_link(SList *list, SList *link)
}
return list;
}
SList *
slist_insert_sorted(SList *list, void *data,
int func(void *data1, void *data2))
{
SList *tmp_list = list,
*prev_list = NULL,
*new_list;
int cmp;
if (!list)
{
new_list = calloc(1, sizeof(SList));
new_list->data = data;
return new_list;
}
cmp = (*func)(data, tmp_list->data);
while ((tmp_list->next) && (cmp > 0))
{
prev_list = tmp_list;
tmp_list = tmp_list->next;
cmp = (*func)(data, tmp_list->data);
}
new_list = calloc(1, sizeof(SList));
new_list->data = data;
if ((!tmp_list->next) && (cmp > 0))
{
tmp_list->next = new_list;
return list;
}
if (prev_list)
{
prev_list->next = new_list;
new_list->next = tmp_list;
return list;
}
else
{
new_list->next = list;
return new_list;
}
}

View File

@ -93,7 +93,11 @@ SList *slist_remove_link(SList *list, SList *link);
SList *slist_nth(SList *list, int n);
void *slist_nth_data(SList *list, int n);
SList *slist_find(SList *list, void *data);
int slist_index(SList *list, void *data);
int slist_length(SList *list);
SList *slist_insert(SList *list, void *data, int position);
SList *slist_insert_sorted(SList *list, void *data,
int func(void *data1, void *data2));
#ifdef __cplusplus
}

Binary file not shown.

View File

@ -19,6 +19,9 @@ MEDIA_DIR=$3
MJPEG_FILE=$4
FIFO_FILE=$5
LOG_FILE=$6
SERVOS_ENABLE=$7
STATE_FILE=/run/pikrellcam/state
if [ "$LOG_FILE" == "" ]
then
@ -33,8 +36,15 @@ WWW_CONFIG=$INSTALL_DIR/www/config.php
PIKRELLCAM=$INSTALL_DIR/pikrellcam
ARCHIVE_LINK=www/archive
MEDIA_LINK=www/media
STATE_LINK=www/state
VERSION=`pikrellcam --version`
if [ ! -h $STATE_LINK ]
then
echo " making $STATE_LINK link to $STATE_FILE" >> $LOG_FILE
ln -s $STATE_FILE $STATE_LINK
fi
if [ ! -h $MEDIA_LINK ]
then
echo " making $MEDIA_LINK link to $MEDIA_DIR" >> $LOG_FILE
@ -103,6 +113,15 @@ else
echo " $WWW_CONFIG: PIKRELLCAM not changed." >> $LOG_FILE
fi
if ! grep -q $SERVOS_ENABLE $WWW_CONFIG
then
CMD="/SERVOS_ENABLE/c\ define\(\"SERVOS_ENABLE\", \"$SERVOS_ENABLE\"\);"
sed -i "$CMD" $WWW_CONFIG
echo " $WWW_CONFIG: SERVOS_ENABLE updated to: $SERVOS_ENABLE" >> $LOG_FILE
else
echo " $WWW_CONFIG: SERVOS_ENABLE not changed." >> $LOG_FILE
fi
if ! fgrep -q $VERSION $WWW_CONFIG
then
CMD="/VERSION/c\ define\(\"VERSION\", \"$VERSION\"\);"

View File

@ -23,7 +23,8 @@ MMAL_LIB ?= -L/opt/vc/lib -lbcm_host -lvcos -lmmal -lmmal_core -lmmal_util \
FLAGS = -O2 -Wall $(MMAL_INCLUDE) $(INCLUDES)
LIBS = $(MMAL_LIB) -lm -lpthread
LOCAL_SRC = pikrellcam.c mmalcam.c motion.c event.c display.c config.c sunriset.c tcpserver.c tcpserver.c tcpserver_mjpeg.c
LOCAL_SRC = pikrellcam.c mmalcam.c motion.c event.c display.c config.c servo.c \
preset.c sunriset.c tcpserver.c tcpserver.c tcpserver_mjpeg.c
KRELLMLIB_SRC = $(wildcard $(addsuffix /*.c,$(LIBKRELLM_DIRS)))
SOURCES = $(LOCAL_SRC) $(KRELLMLIB_SRC)

View File

@ -1,6 +1,6 @@
/* PiKrellCam
|
| Copyright (C) 2015 Bill Wilson billw@gkrellm.net
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
|
| PiKrellCam is free software: you can redistribute it and/or modify it
| under the terms of the GNU General Public License as published by
@ -512,7 +512,7 @@ config_value_int_set(char *arg, ConfigResult *result)
{
int valid = TRUE;
if (isdigit(*arg))
if (isdigit(*arg) || (*arg == '-' && isdigit((*(arg + 1)))))
*result->value = atoi(arg);
else
{
@ -533,15 +533,6 @@ static Config config[] =
"#",
"install_dir", "/home/pi/pikrellcam", TRUE, { .string = &pikrellcam.install_dir }, config_string_set },
{ "# Directory for the stream mjpeg file and info file. These files\n"
"# are frequently updated so this directory should be in a tmpfs.\n"
"# This could be a directory in /tmp if your /tmp is a tmpfs.\n"
"# Avoid putting this directory under /run/shm or /dev/shm because these\n"
"# directories are subject to automatic cleanups which could delete the\n"
"# tmpfs_dir out from under a running pikrellcam if running headless.\n"
"#",
"tmpfs_dir", "/run/pikrellcam", TRUE, { .string = &pikrellcam.tmpfs_dir }, config_string_set },
{ "# If media_dir has no leading '/' it will be a sub directory in install_dir.\n"
"# Otherwise it is a full pathname to the media directory.\n"
"# So the default media dir is /home/pi/pikrellcam/media.\n"
@ -568,7 +559,7 @@ static Config config[] =
{ "# At startup and at each new day, trim the log file number of lines\n"
"# to log_lines. If log_lines is 0 the log file is not trimmed.\n"
"#",
"log_lines", "1000", FALSE, {.value = &pikrellcam.log_lines}, config_value_int_set},
"log_lines", "500", FALSE, {.value = &pikrellcam.log_lines}, config_value_int_set},
{ "# Command to run at PiKrellCam startup. This is run after the config\n"
"# files are loaded but before the camera is started or directories\n"
@ -582,28 +573,24 @@ static Config config[] =
{ "\n# -------------------- Motion Detect Options -----------------------\n"
"# PiKrellCam V3.0 stores some motion detect settings in preset-xxx.conf\n"
"# Vector and burst limits/counts are no longer saved in pikrellcam.conf.\n"
"#\n"
"# Enable pikrellcam motion detection at startup\n"
"#",
"motion_enable", "off", FALSE, {.value = &pikrellcam.motion_enable}, config_value_bool_set},
"motion_enable", "off", FALSE, {.value = &pikrellcam.motion_enable}, config_value_bool_set},
{ "# Motion vectors must be at least this magnitude for a motion detect.\n"
"# Minimum is 3 for detecting the slowest moving objects possible.\n"
{ "# If off, do not detect motion when servos are off a preset.\n"
"#",
"motion_off_preset", "off", FALSE, {.value = &pikrellcam.motion_off_preset}, config_value_bool_set},
{ "#OBSOLETE",
"motion_magnitude_limit", "5", FALSE, {.value = &pikrellcam.motion_magnitude_limit}, config_value_int_set},
{ "# The count of vectors required for a motion detect.\n"
"# Minimum is 2 for detecting the smallest objects possible.\n"
"#",
{ "#OBSOLETE",
"motion_magnitude_limit_count", "4", FALSE, {.value = &pikrellcam.motion_magnitude_limit_count}, config_value_int_set},
{ "# Motion vector count minimum for a burst motion detect.\n"
"# For large/close object detection.\n"
"#",
{ "#OBSOLETE",
"motion_burst_count", "400", FALSE, {.value = &pikrellcam.motion_burst_count}, config_value_int_set},
{ "# The number of sustained frames for a burst motion detect.\n"
"#",
{ "#OBSOLETE",
"motion_burst_frames", "3", FALSE, {.value = &pikrellcam.motion_burst_frames}, config_value_int_set},
{ "# Time length limit of motion video record excluding pre_capture time.\n"
@ -686,12 +673,16 @@ static Config config[] =
"#",
"motion_preview_clean", "on", FALSE, {.value = &pikrellcam.motion_preview_clean}, config_value_bool_set },
{ "# If on, show extra vector count data on the OSD when presets are shown.\n"
"#",
"motion_show_counts", "off", FALSE, {.value = &pikrellcam.motion_show_counts}, config_value_bool_set },
{ "# Minimum width and height in pixels for the substitution width and height\n"
"# variables for motion detect areas in the preview jpeg.\n"
"# This minimum helps with possible frame skew for smaller relatively\n"
"# faster moving objects.\n"
"#",
"motion_area_min_side", "60", FALSE, {.value = &pikrellcam.motion_area_min_side}, config_value_int_set },
"motion_area_min_side", "80", FALSE, {.value = &pikrellcam.motion_area_min_side}, config_value_int_set },
{ "# Enable writing a motion statistics .csv file for each motion video.\n"
"# For users who have a need for advanced video post processing.\n"
@ -774,10 +765,11 @@ static Config config[] =
"video_fps", "24", FALSE, {.value = &pikrellcam.camera_adjust.video_fps}, config_value_int_set },
{ "# MP4Box output frames per second if video filename is a .mp4\n"
"# If this is different from video_fps, the final mp4 will be a\n"
"# slow or fast motion video.\n"
"# If this is non zero and different from video_fps, the final mp4 will\n"
"# be a slow or fast motion video.\n"
"# Normally leave this set to zero so it will track video_fps.\n"
"#",
"video_mp4box_fps", "24", FALSE, {.value = &pikrellcam.camera_adjust.video_mp4box_fps}, config_value_int_set },
"video_mp4box_fps", "0", FALSE, {.value = &pikrellcam.camera_adjust.video_mp4box_fps}, config_value_int_set },
{ "# Video bitrate affects the quality and size of a video recording.\n"
"# Along with pre_capture and event_gap times, it also determines the\n"
@ -790,13 +782,18 @@ static Config config[] =
"video_bitrate", "6000000", TRUE, {.value = &pikrellcam.camera_adjust.video_bitrate}, config_value_int_set },
{ "# Pixel width of the stream jpeg file. Aspect ratio is determined by the video.\n"
"# This value will be rounded off to be a multiple of 16. If you want\n"
"# a larger image but not increase bandwith usage, try a mjpeg_width of\n"
"# 1024 with a mjpeg_quality 10.\n"
"#",
"mjpeg_width", "640", TRUE, {.value = &pikrellcam.mjpeg_width}, config_value_int_set },
"mjpeg_width", "800", TRUE, {.value = &pikrellcam.mjpeg_width}, config_value_int_set },
{ "# Quality factor (up to 100) affects the quality and size of the stream jpeg.\n"
"# Set this lower if you need to reduce the stream bandwidth.\n"
"# Set this lower if you need to reduce the stream bandwidth. The value\n"
"# is not the same as quality factors in other jpeg programs and should\n"
"# be set lower than those programs.\n"
"#",
"mjpeg_quality", "14", TRUE, {.value = &pikrellcam.mjpeg_quality}, config_value_int_set },
"mjpeg_quality", "10", TRUE, {.value = &pikrellcam.mjpeg_quality}, config_value_int_set },
{ "# Divide the video_fps by this to get the stream jpeg file update rate.\n"
"# This will also be the motion frame check rate for motion detection.\n"
@ -835,7 +832,7 @@ static Config config[] =
{ "# This quality factor affects the size and quality of still captures.\n"
"#",
"still_quality", "30", TRUE, {.value = &pikrellcam.camera_adjust.still_quality}, config_value_int_set },
"still_quality", "14", TRUE, {.value = &pikrellcam.camera_adjust.still_quality}, config_value_int_set },
{ "# Command to run after a still capture.\n"
"# email the still somewhere with:\n"
@ -874,6 +871,74 @@ static Config config[] =
{.string = &pikrellcam.timelapse_convert_cmd}, config_string_set },
{ "\n# ------------------- Servo/Preset Options -----------------------\n"
"#\n"
"# PiKrellCam can use internal hardware PWM code to drive servos and for\n"
"# this there is no extra install required.\n"
"# For hardware PWM, the pan/tilt or tilt/pan gpio pairs must be one of\n"
"# 12,13 12,19 18,13 18,19\n"
"# and these are Pi hardware gpio header pin numbers.\n"
"# \n"
"# Or, PiKrellCam can use ServoBlaster and will then send servo commands\n"
"# to /dev/servoblaster. But for this, a separate ServoBlaster install\n"
"# and configuration to run is required.\n"
"# For ServoBlaster, the PiKrellCam servo pan/tilt gpio values should not\n"
"# be Pi header gpio numbers but instead should be one of the ServoBlaster\n"
"# documented servo numbers 0 - 7. See ServoBlaster documentation.\n"
"# \n"
"# Leave the gpios at -1 if not using servos.\n"
"#",
"servo_pan_gpio", "-1", FALSE, {.value = &pikrellcam.servo_pan_gpio}, config_value_int_set },
{ "#",
"servo_tilt_gpio", "-1", FALSE, {.value = &pikrellcam.servo_tilt_gpio}, config_value_int_set },
{ "# Set to true to use ServoBlaster for servos. A separate install of\n"
"# ServoBlaster will be required.\n"
"#",
"servo_use_servoblaster", "false", TRUE, {.value = &pikrellcam.servo_use_servoblaster }, config_value_bool_set },
{ "# pan/tilt min/max values are best set using the web OSD.\n"
"# The value units are 0.01 msec, so for example, a servo_pan_min of\n"
"# 120 would limit the pan servo control pulse to a 1.2 msec minimum.\n"
"#",
"servo_pan_min", "120", FALSE, {.value = &pikrellcam.servo_pan_min}, config_value_int_set },
{ "#",
"servo_pan_max", "180", FALSE, {.value = &pikrellcam.servo_pan_max}, config_value_int_set },
{ "#",
"servo_tilt_min", "130", FALSE, {.value = &pikrellcam.servo_tilt_min}, config_value_int_set },
{ "#",
"servo_tilt_max", "170", FALSE, {.value = &pikrellcam.servo_tilt_max}, config_value_int_set },
{ "# Set invert values to on if the servo turns the wrong way.\n"
"#",
"servo_pan_invert", "off", FALSE, {.value = &pikrellcam.servo_pan_invert}, config_value_bool_set },
{ "#",
"servo_tilt_invert", "off", FALSE, {.value = &pikrellcam.servo_tilt_invert}, config_value_bool_set },
{ "# Servo moves have three modes: move by one step, by servo_move_steps,\n"
"# or move continuous until stopped or a min/max limit is reached.\n"
"# Set here the number of steps wanted for the second mode.\n"
"#",
"servo_move_steps", "10", FALSE, {.value = &pikrellcam.servo_move_steps }, config_value_int_set },
{ "# Delay in msec between servo pulse width steps for servo move commands.\n"
"#",
"servo_move_step_msec", "40", FALSE, {.value = &pikrellcam.servo_move_step_msec }, config_value_int_set },
{ "# Delay in msec between servo pulse width steps when going to a preset.\n"
"#",
"servo_preset_step_msec", "20", FALSE, {.value = &pikrellcam.servo_preset_step_msec }, config_value_int_set },
{ "# Delay in msec after servo stops moving before enabling motion detection.\n"
"#",
"servo_settle_msec", "600", FALSE, {.value = &pikrellcam.servo_settle_msec }, config_value_int_set },
{ "\n# ------------------- Miscellaneous Options -----------------------\n"
"#\n"
"# How long in seconds a notify string should stay on the stream jpeg file.\n"
@ -926,7 +991,7 @@ static Config config[] =
{ "# Annotate text size. Range: integer from 6 - 160\n"
"#",
"annotate_text_size", "32", FALSE, {.value = &pikrellcam.annotate_text_size }, config_value_int_set },
"annotate_text_size", "40", FALSE, {.value = &pikrellcam.annotate_text_size }, config_value_int_set },
};
#define CONFIG_SIZE (sizeof(config) / sizeof(Config))
@ -959,11 +1024,10 @@ config_set_option(char *opt, char *arg, boolean set_safe)
}
void
config_set_defaults(void)
config_set_defaults(char *home_dir)
{
CameraParameter *param;
Config *cfg;
char *home_dir;
boolean valid;
/* Camera parameter table and pikrellcam config table have initial value pointers
@ -992,8 +1056,10 @@ config_set_defaults(void)
/* If pikrellcam started by rc.local or web page, need to get correct
| home directory. Makefile does a setuid/setgid on executable.
*/
home_dir = getpwuid(geteuid())->pw_dir;
if (!home_dir)
home_dir = getpwuid(geteuid())->pw_dir;
asprintf(&pikrellcam.config_dir, "%s/%s", home_dir, PIKRELLCAM_CONFIG_DIR);
pikrellcam.tmpfs_dir = strdup("/run/pikrellcam");
if (make_directory(pikrellcam.config_dir))
{
@ -1014,7 +1080,7 @@ config_set_defaults(void)
motion_command("add_region 0.266 0.159 0.233 0.756");
motion_command("add_region 0.500 0.150 0.233 0.750");
motion_command("add_region 0.734 0.156 0.224 0.753");
motion_frame.show_regions = FALSE;
motion_frame.show_preset = FALSE;
}
boolean
@ -1028,7 +1094,7 @@ config_load(char *config_file)
if ((f = fopen(config_file, "r")) == NULL)
return FALSE;
pikrellcam.config_sequence_new = 15;
pikrellcam.config_sequence_new = 31;
while (fgets(linebuf, sizeof(linebuf), f))
{
@ -1048,6 +1114,9 @@ config_load(char *config_file)
}
fclose(f);
/* Round off mjpeg_width to multiple of 16 */
pikrellcam.mjpeg_width = (pikrellcam.mjpeg_width + 8) & ~0xf;
if (pikrellcam.motion_magnitude_limit < 3)
pikrellcam.motion_magnitude_limit = 3;
if (pikrellcam.motion_magnitude_limit_count < 2)
@ -1085,6 +1154,22 @@ config_load(char *config_file)
camera_adjust_temp = pikrellcam.camera_adjust;
motion_times_temp = pikrellcam.motion_times;
if ( (pikrellcam.servo_use_servoblaster && pikrellcam.servo_pan_gpio >= 0)
|| ( !pikrellcam.servo_use_servoblaster
&& ( pikrellcam.servo_pan_gpio == 12 || pikrellcam.servo_pan_gpio == 13
|| pikrellcam.servo_pan_gpio == 18 || pikrellcam.servo_pan_gpio == 19
)
)
)
pikrellcam.have_servos = TRUE;
asprintf(&pikrellcam.preset_config_file, "%s/preset-%s.conf",
pikrellcam.config_dir,
pikrellcam.have_servos ? "servos" : "no-servos");
asprintf(&pikrellcam.preset_state_file, "%s/preset-%s.state",
pikrellcam.config_dir,
pikrellcam.have_servos ? "servos" : "no-servos");
if (pikrellcam.config_sequence_new != pikrellcam.config_sequence)
{
pikrellcam.config_sequence = pikrellcam.config_sequence_new;
@ -1128,6 +1213,8 @@ config_save(char *config_file)
);
for (cfg = &config[0]; cfg < &config[CONFIG_SIZE]; ++cfg)
{
if (!strncmp("#OBSOLETE", cfg->description, 9)) /* do not save */
continue;
fprintf(f, "%s\n", cfg->description);
if (cfg->config_func == config_value_int_set)
fprintf(f, "%s %d\n\n", cfg->option, *(cfg->result.value));

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* PiKrellCam
|
| Copyright (C) 2015 Bill Wilson billw@gkrellm.net
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
|
| PiKrellCam is free software: you can redistribute it and/or modify it
| under the terms of the GNU General Public License as published by
@ -31,6 +31,8 @@ static pthread_mutex_t event_mutex;
static SList *event_list,
*at_command_list;
static boolean exec_with_session = TRUE;
typedef struct
{
boolean initialized;
@ -59,6 +61,13 @@ typedef struct
static Sun sun;
void
set_exec_with_session(boolean set)
{
exec_with_session = set;
}
/* exec a command with the given arg. Any strftime() % replacements should
| have been done before calling this so there will remain only pikrellcam
| specific $X conversions. Change all '$X' to '%s' and printf in what we
@ -67,10 +76,11 @@ static Sun sun;
static int
exec_command(char *command, char *arg, boolean wait, pid_t *pid)
{
struct tm *tm_now;
CompositeVector *frame_vec = &motion_frame.final_preview_vector;
char specifier, *fmt, *fmt_arg, *copy, *cmd_line, *name, buf[BUFSIZ];
int t, i, status = 0;
struct tm *tm_now;
PresetPosition *pos;
CompositeVector *frame_vec = &motion_frame.final_preview_vector;
char specifier, *fmt, *fmt_arg, *copy, *cmd_line, *name, buf[BUFSIZ];
int t, i, status = 0;
if (!command || !*command)
return -1;
@ -147,6 +157,14 @@ exec_command(char *command, char *arg, boolean wait, pid_t *pid)
case 'P':
fmt_arg = pikrellcam.command_fifo;
break;
case 'p':
pos = (PresetPosition *) slist_nth_data(pikrellcam.preset_position_list,
pikrellcam.preset_position_index);
snprintf(buf, sizeof(buf), "%d %d",
pikrellcam.preset_position_index + 1,
pos ? pos->settings_index + 1 : 1);
fmt_arg = buf;
break;
case 'c':
fmt_arg = pikrellcam.scripts_dist_dir;
break;
@ -212,7 +230,8 @@ exec_command(char *command, char *arg, boolean wait, pid_t *pid)
{ /* child - execute command in background */
for (i = getdtablesize(); i > 2; --i)
close(i);
setsid(); /* new session group - ie detach */
if (exec_with_session)
setsid(); /* new session group - ie detach */
execl("/bin/sh", "sh", "-c", cmd_line, " &", NULL);
_exit (EXIT_FAILURE);
}
@ -429,15 +448,49 @@ state_file_write(void)
{
static char *fname_part;
FILE *f;
MotionFrame *mf = &motion_frame;
VideoCircularBuffer *vcb = &video_circular_buffer;
PresetPosition *pos;
PresetSettings *settings = NULL;
char *state;
int pan, tilt;
if (!fname_part)
asprintf(&fname_part, "%s.part", pikrellcam.state_filename);
f = fopen(fname_part, "w");
fprintf(f, "motion_enable %s\n",
motion_frame.motion_enable ? "on" : "off");
fprintf(f, "motion_enable %s\n", mf->motion_enable ? "on" : "off");
fprintf(f, "show_preset %s\n", mf->show_preset ? "on" : "off");
fprintf(f, "show_vectors %s\n", mf->show_vectors ? "on" : "off");
servo_get_position(&pan, &tilt);
pos = preset_find_at_position(pan, tilt);
if (pos)
{
fprintf(f, "preset %d %d\n", pikrellcam.preset_position_index + 1,
pos ? pos->settings_index + 1 : 1);
settings = (PresetSettings *) slist_nth_data(pos->settings_list, pos->settings_index);
if (settings)
{
fprintf(f, "magnitude_limit %d\n", settings->mag_limit);
fprintf(f, "magnitude_count %d\n", settings->mag_limit_count);
fprintf(f, "burst_count %d\n", settings->burst_count);
fprintf(f, "burst_frames %d\n", settings->burst_frames);
}
}
else
{
fprintf(f, "preset 0 0\n");
fprintf(f, "magnitude_limit 0\n");
fprintf(f, "magnitude_count 0\n");
fprintf(f, "burst_count 0\n");
fprintf(f, "burst_frames 0\n");
}
if (pikrellcam.have_servos)
{
fprintf(f, "pan %d\n", pan);
fprintf(f, "tilt %d\n", tilt);
}
if (vcb->state & VCB_STATE_MOTION)
state = "motion";
@ -452,6 +505,7 @@ state_file_write(void)
fprintf(f, "still_last %s\n",
pikrellcam.still_last ? pikrellcam.still_last : "none");
fprintf(f, "show_timelapse %s\n", time_lapse.show_status ? "on" : "off");
fprintf(f, "timelapse_period %d\n", time_lapse.period);
fprintf(f, "timelapse_active %s\n",
time_lapse.activated ? "on" : "off");
@ -490,6 +544,7 @@ event_count_down_add(char *name, int count,
event = calloc(1, sizeof(Event));
event->name = name;
event->count = count;
event->period = 0; /* one time event */
event->func = func;
event->data = data;
@ -498,8 +553,8 @@ event_count_down_add(char *name, int count,
pthread_mutex_unlock(&event_mutex);
if (pikrellcam.verbose)
printf("Event add [%s] period=%d\n",
event->name, (int) event->period);
printf("Event count down add [%s] count=%d\n",
event->name, (int) event->count);
return event;
}
@ -529,6 +584,17 @@ event_add(char *name, time_t time, time_t period,
return event;
}
void
event_list_lock(void)
{
pthread_mutex_lock(&event_mutex);
}
void
event_list_unlock(void)
{
pthread_mutex_unlock(&event_mutex);
}
Event *
event_find(char *name)
@ -846,6 +912,8 @@ event_process(void)
start = FALSE;
if (pikrellcam.config_modified)
config_save(pikrellcam.config_file);
if (pikrellcam.preset_modified)
preset_config_save();
}
}

View File

@ -1,6 +1,6 @@
/* PiKrellCam
|
| Copyright (C) 2015 Bill Wilson billw@gkrellm.net
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
|
| PiKrellCam is free software: you can redistribute it and/or modify it
| under the terms of the GNU General Public License as published by
@ -186,8 +186,8 @@ mjpeg_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
static FILE *file = NULL;
static char *fname_part;
boolean do_preview_save = FALSE;
static char *tcp_buf;
static int tcp_buf_offset;
static char *tcp_buf;
static int tcp_buf_offset;
if (!fname_part)
@ -200,10 +200,11 @@ mjpeg_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
{
mmal_buffer_header_mem_lock(buffer);
n = fwrite(buffer->data, 1, buffer->length, file);
if (tcp_buf) {
if (tcp_buf)
{
memcpy(tcp_buf + tcp_buf_offset, buffer->data, buffer->length);
tcp_buf_offset += buffer->length;
}
}
mmal_buffer_header_mem_unlock(buffer);
if (n != buffer->length)
{
@ -213,11 +214,12 @@ mjpeg_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
}
if (buffer->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END)
{
if (tcp_buf) {
if (tcp_buf)
{
mjpeg_server_queue_put(tcp_buf, tcp_buf_offset);
tcp_buf = NULL;
tcp_buf_offset = 0;
}
}
if (debug_fps && (utime = micro_elapsed_time(&timer)) > 0)
printf("%s fps %d\n", data->name, 1000000 / utime);
@ -226,16 +228,16 @@ mjpeg_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
fclose(file);
file = NULL;
pthread_mutex_lock(&mjpeg_encoder_count_lock);
++mjpeg_encoder_recv_count;
if (mjpeg_do_preview_save == 1)
{
mjpeg_do_preview_save = 0;
do_preview_save = TRUE;
}
else if (mjpeg_do_preview_save > 1)
--mjpeg_do_preview_save;
pthread_mutex_unlock(&mjpeg_encoder_count_lock);
pthread_mutex_lock(&mjpeg_encoder_count_lock);
++mjpeg_encoder_recv_count;
if (mjpeg_do_preview_save == 1)
{
mjpeg_do_preview_save = 0;
do_preview_save = TRUE;
}
else if (mjpeg_do_preview_save > 1)
--mjpeg_do_preview_save;
pthread_mutex_unlock(&mjpeg_encoder_count_lock);
/* When adding an event_preview_save, set a rename holdoff that
| will be reset when the preview_save is done. Don't do
@ -280,11 +282,13 @@ still_jpeg_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
{
CameraObject *data = (CameraObject *) port->userdata;
int n;
static int bytes_written;
if (buffer->length && still_jpeg_encoder.file)
{
mmal_buffer_header_mem_lock(buffer);
n = fwrite(buffer->data, 1, buffer->length, still_jpeg_encoder.file);
bytes_written += n;
mmal_buffer_header_mem_unlock(buffer);
if (n != buffer->length)
{
@ -295,12 +299,25 @@ still_jpeg_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
if (buffer->flags & MMAL_BUFFER_HEADER_FLAG_FRAME_END)
{
fclose(still_jpeg_encoder.file);
still_jpeg_encoder.file = NULL;
if (pikrellcam.still_capture_event)
event_add("still capture command", pikrellcam.t_now, 0,
event_still_capture_cmd,
pikrellcam.on_still_capture_cmd);
else if (pikrellcam.timelapse_capture_event)
{
if (bytes_written > 0)
time_lapse.sequence += 1;
else if (pikrellcam.timelapse_jpeg_last)
{
unlink(pikrellcam.timelapse_jpeg_last);
dup_string(&pikrellcam.timelapse_jpeg_last, "failed");
}
}
pikrellcam.still_capture_event = FALSE;
pikrellcam.timelapse_capture_event = FALSE;
bytes_written = 0;
pikrellcam.state_modified = TRUE;
still_jpeg_encoder.file = NULL;
}
return_buffer_to_port(port, buffer);
}

View File

@ -1,6 +1,6 @@
/* PiKrellCam
|
| Copyright (C) 2015 Bill Wilson billw@gkrellm.net
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
|
| PiKrellCam is free software: you can redistribute it and/or modify it
| under the terms of the GNU General Public License as published by
@ -578,8 +578,12 @@ motion_frame_process(VideoCircularBuffer *vcb, MotionFrame *mf)
if (motion_burst_frame == pikrellcam.motion_burst_frames)
motion_burst_frame = 0;
motion_enabled = mf->motion_enable
|| (mf->external_trigger_mode & EXT_TRIG_MODE_ENABLE);
motion_enabled = ( ( mf->motion_enable
|| (mf->external_trigger_mode & EXT_TRIG_MODE_ENABLE)
)
&& !pikrellcam.servo_moving
&& (pikrellcam.on_preset || pikrellcam.motion_off_preset)
);
if ( ( (mf->motion_status & MOTION_DETECTED)
&& motion_enabled
@ -782,22 +786,23 @@ atof_range(float *result, char *value, double low, double high)
return FALSE;
}
#define ADD_REGION 0
#define MOVE_REGION 1
#define MOVE_COARSE 2
#define MOVE_FINE 3
#define ASSIGN_REGION 4
#define SAVE_REGIONS 5
#define LOAD_REGIONS 6
#define LOAD_REGIONS_SHOW 7
#define LIST_REGIONS 8
#define DELETE_REGIONS 9
#define SET_LIMITS 10
#define SET_BURST 11
#define SELECT_REGION 12
#define SHOW_REGIONS 13
#define SHOW_VECTORS 14
#define TRIGGER 15
#define NEW_REGION 0
#define ADD_REGION 1
#define MOVE_REGION 2
#define MOVE_COARSE 3
#define MOVE_FINE 4
#define ASSIGN_REGION 5
#define SAVE_REGIONS 6
#define LOAD_REGIONS 7
#define LOAD_REGIONS_SHOW 8
#define LIST_REGIONS 9
#define DELETE_REGIONS 10
#define SET_LIMITS 11
#define SET_BURST 12
#define SELECT_REGION 13
#define SHOW_PRESET 14
#define SHOW_VECTORS 15
#define TRIGGER 16
typedef struct
{
@ -809,9 +814,11 @@ typedef struct
static MotionCommand motion_commands[] =
{
{ "show_regions", SHOW_REGIONS, 1 },
{ "show_preset", SHOW_PRESET, 1 },
{ "show_regions", SHOW_PRESET, 1 },
{ "show_vectors", SHOW_VECTORS, 1 },
{ "add_region", ADD_REGION, 4 },
{ "new_region", NEW_REGION, 4 },
{ "add_region", ADD_REGION, 4 }, // Not a regions modify
{ "move_region", MOVE_REGION, 5 },
{ "move_coarse", MOVE_COARSE, 2 },
{ "move_fine", MOVE_FINE, 2 },
@ -820,7 +827,7 @@ static MotionCommand motion_commands[] =
{ "load_regions", LOAD_REGIONS, 1 },
{ "load_regions_show", LOAD_REGIONS_SHOW, 1 },
{ "list_regions", LIST_REGIONS, 0 },
{ "delete_regions", DELETE_REGIONS, 1 },
{ "delete_regions", DELETE_REGIONS, 1 }, // All not a regions modify
{ "select_region", SELECT_REGION, 1 },
{ "limits", SET_LIMITS, 2 },
{ "burst", SET_BURST, 2 },
@ -907,6 +914,7 @@ motion_command(char *cmd_line)
float delta = 0.0;
struct dirent *dp;
DIR *dfd;
boolean new_region = FALSE;
arg1[0] = '\0';
arg2[0] = '\0';
@ -934,13 +942,20 @@ motion_command(char *cmd_line)
switch (id)
{
case SHOW_REGIONS:
config_set_boolean(&mf->show_regions, arg1);
case SHOW_PRESET:
config_set_boolean(&mf->show_preset, arg1);
pikrellcam.state_modified = TRUE;
break;
case SHOW_VECTORS:
config_set_boolean(&mf->show_vectors, arg1);
pikrellcam.state_modified = TRUE;
break;
case ADD_REGION: /* add_region x y dx dy */
/* A new_region is an edit modify from the web page.
| An add_region is a load from a config file and is not a modify.
*/
case NEW_REGION: /* new_region x y dx dy */
new_region = TRUE;
case ADD_REGION: /* load_region x y dx dy */
memset((char *) &mrtmp, 0, sizeof(MotionRegion));
if (get_motion_args(&mrtmp, arg1, arg2, arg3, arg4, 0.00, 1.0))
{
@ -953,7 +968,10 @@ motion_command(char *cmd_line)
mf->n_regions = slist_length(mf->motion_region_list);
mreg->region_number = mf->n_regions - 1;
mf->selected_region = mreg->region_number;
mf->show_regions = TRUE;
mf->show_preset = TRUE;
pikrellcam.state_modified = TRUE;
if (new_region)
preset_regions_set_modified();
pthread_mutex_unlock(&mf->region_list_mutex);
}
else
@ -976,7 +994,8 @@ motion_command(char *cmd_line)
mf->selected_region = 0;
}
mf->prev_selected_region = mf->selected_region;
mf->show_regions = TRUE;
mf->show_preset = TRUE;
pikrellcam.state_modified = TRUE;
}
break;
@ -1007,6 +1026,7 @@ motion_command(char *cmd_line)
mreg->dyf += delta;
motion_region_fixup(mreg);
}
preset_regions_set_modified();
pthread_mutex_unlock(&mf->region_list_mutex);
}
else
@ -1029,6 +1049,7 @@ motion_command(char *cmd_line)
mreg->dyf += mrtmp.dyf;
motion_region_fixup(mreg);
}
preset_regions_set_modified();
pthread_mutex_unlock(&mf->region_list_mutex);
break;
@ -1045,6 +1066,7 @@ motion_command(char *cmd_line)
mreg->dyf = mrtmp.dyf;
motion_region_fixup(mreg);
}
preset_regions_set_modified();
pthread_mutex_unlock(&mf->region_list_mutex);
break;
@ -1055,10 +1077,12 @@ motion_command(char *cmd_line)
break;
case LOAD_REGIONS_SHOW: /* load_regions and show */
mf->show_regions = TRUE;
case LOAD_REGIONS: /* load_regions config-name */
mf->show_preset = TRUE;
pikrellcam.state_modified = TRUE;
case LOAD_REGIONS: /* load_regions config-name */
path = regions_custom_config(arg1);
motion_regions_config_load(path, TRUE);
if (motion_regions_config_load(path, TRUE))
preset_regions_set_modified();
free(path);
break;
@ -1086,7 +1110,10 @@ motion_command(char *cmd_line)
closedir(dfd);
}
break;
/* An all delete is a clear prior to a load from a config file and
| is not a modify. A delete of a region number is a delete from the
| from the web page and is an edit modify of the regions.
*/
case DELETE_REGIONS: /* delete_regions all / delete r */
pthread_mutex_lock(&mf->region_list_mutex);
if (!strcmp(arg1, "all"))
@ -1108,6 +1135,7 @@ motion_command(char *cmd_line)
mf->motion_region_list =
slist_remove(mf->motion_region_list, mreg);
free(mreg);
preset_regions_set_modified();
}
else
{
@ -1144,6 +1172,8 @@ motion_command(char *cmd_line)
if (n > mf->width * mf->height / 2)
n = n > mf->width * mf->height / 2;
pikrellcam.motion_magnitude_limit_count = n;
preset_settings_set_modified();
break;
case SET_BURST: /* burst count frames */
@ -1160,6 +1190,8 @@ motion_command(char *cmd_line)
if (n > 20)
n = 20;
pikrellcam.motion_burst_frames = n;
preset_settings_set_modified();
break;
case TRIGGER:
@ -1278,7 +1310,7 @@ motion_regions_config_load(char *config_file, boolean inform)
return FALSE;
}
save_show = motion_frame.show_regions;
save_show = motion_frame.show_preset;
motion_command("delete_regions all");
while (fgets(buf, sizeof(buf), f) != NULL)
@ -1292,6 +1324,6 @@ motion_regions_config_load(char *config_file, boolean inform)
display_inform(dbuf);
display_inform("timeout 1");
}
motion_frame.show_regions = save_show; /* can't double send_cmd()??*/
motion_frame.show_preset = save_show; /* can't double send_cmd()??*/
return TRUE;
}

View File

@ -1,6 +1,6 @@
/* PiKrellCam
|
| Copyright (C) 2015 Bill Wilson billw@gkrellm.net
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
|
| PiKrellCam is free software: you can redistribute it and/or modify it
| under the terms of the GNU General Public License as published by
@ -32,6 +32,10 @@ extern int setup_mjpeg_tcp_server(void);
static char *pgm_name;
static boolean quit_flag;
static uid_t user_uid;
static gid_t user_gid;
static char *homedir;
/* Substitute fmt_arg into str to replace a "$V" substitution variable.
| "str" argument must be allocated memory.
*/
@ -296,7 +300,7 @@ still_capture(char *fname)
/* timelapse_shapshot() also uses the still_jpeg_encoder, so wait if busy.
*/
for (n = 0; n < 5; ++n)
for (n = 0; n < 10; ++n)
{
if (still_jpeg_encoder.file == NULL)
break;
@ -327,12 +331,13 @@ still_capture(char *fname)
result = TRUE;
log_printf("Still: %s\n", fname);
dup_string(&pikrellcam.still_last, fname);
pikrellcam.state_modified = TRUE;
n = pikrellcam.notify_duration * EVENT_LOOP_FREQUENCY;
event_list_lock();
if ((event = event_find("still saved")) != NULL)
event->count = n; /* rapid stills, extend the time */
else
event_list_unlock();
if (!event)
event_count_down_add("still saved", n,
event_notify_expire, &pikrellcam.still_notify);
pikrellcam.still_capture_event = TRUE;
@ -354,7 +359,7 @@ timelapse_capture(void)
/* still_capture() also uses the still_jpeg_encoder, so wait if busy.
*/
for (n = 0; n < 5; ++n)
for (n = 0; n < 10; ++n)
{
if (still_jpeg_encoder.file == NULL)
break;
@ -372,26 +377,28 @@ timelapse_capture(void)
pikrellcam.timelapse_format,
'N', seq_buf,
'n', series_buf);
time_lapse.sequence += 1;
if ((still_jpeg_encoder.file = fopen(path, "w")) == NULL)
log_printf("Could not create timelapse file %s. %m\n", path);
else
{
dup_string(&pikrellcam.timelapse_jpeg_last, path);
pikrellcam.timelapse_capture_event = TRUE;
if ((status = mmal_port_parameter_set_boolean(
camera.component->output[CAMERA_CAPTURE_PORT],
MMAL_PARAMETER_CAPTURE, 1)) != MMAL_SUCCESS)
{
fclose(still_jpeg_encoder.file);
unlink(path);
still_jpeg_encoder.file = NULL;
dup_string(&pikrellcam.timelapse_jpeg_last, "failed");
pikrellcam.timelapse_capture_event = FALSE;
log_printf("Timelapse capture startup failed. Status %s\n",
mmal_status[status]);
}
else
{
log_printf("Timelapse still: %s\n", path);
dup_string(&pikrellcam.timelapse_jpeg_last, path);
pikrellcam.state_modified = TRUE;
/* timelapse_capture() is an event call (inside the event loop)
| and we here add an event to the list.
@ -574,7 +581,9 @@ video_record_stop(VideoCircularBuffer *vcb)
asprintf(&cmd, "(MP4Box %s -tmp %s -fps %d -add %s %s %s && rm %s %s)",
pikrellcam.verbose ? "" : "-quiet",
tmp_dir,
pikrellcam.camera_adjust.video_mp4box_fps,
(pikrellcam.camera_adjust.video_mp4box_fps > 0) ?
pikrellcam.camera_adjust.video_mp4box_fps :
pikrellcam.camera_adjust.video_fps,
pikrellcam.video_h264, pikrellcam.video_pathname,
pikrellcam.verbose ? "" : "2> /dev/null",
pikrellcam.video_h264,
@ -643,7 +652,7 @@ get_arg_pass1(char *arg)
if (!strcmp(arg, "-V") || !strcmp(arg, "--version"))
{
printf("%s\n", pikrellcam.version);
printf("%s\n", PIKRELLCAM_VERSION);
exit(0);
}
else if (!strcmp(arg, "-h") || !strcmp(arg, "--help"))
@ -651,25 +660,18 @@ get_arg_pass1(char *arg)
/* XXX */
exit(0);
}
#if 0
else if (!strcmp(arg, "-d") || !strcmp(arg, "--detach"))
{
if (getppid() != 1) /* if not already a daemon */
{
if (daemon(0, 0)) /* Detach from terminal, reparent to pid 1 */
{
printf("Detach failed\n");
exit(1);
}
}
}
#endif
else if (!strcmp(arg, "-v"))
pikrellcam.verbose = TRUE;
else if (!strcmp(arg, "-vm"))
pikrellcam.verbose_motion = TRUE;
else if (!strcmp(arg, "-debug"))
pikrellcam.debug = TRUE;
else if (!strncmp(arg, "-user", 5))
user_uid = atoi(arg + 5);
else if (!strncmp(arg, "-group", 6))
user_gid = atoi(arg + 6);
else if (!strncmp(arg, "-home", 5))
homedir = strdup(arg + 5);
else
return FALSE;
return TRUE;
@ -704,6 +706,8 @@ typedef enum
annotate_string,
delete_log,
fix_thumbs,
servo_cmd,
preset_cmd,
upgrade,
quit
}
@ -730,7 +734,7 @@ static Command commands[] =
{ "tl_hold", tl_hold, 1, TRUE },
{ "tl_show_status", tl_show_status, 1, FALSE },
{ "motion", motion_cmd, 1, TRUE },
{ "motion", motion_cmd, 1, FALSE },
{ "motion_enable", motion_enable, 1, TRUE },
/* Above commands are redirected to abort a menu or adjustment display
@ -751,6 +755,8 @@ static Command commands[] =
{ "delete_log", delete_log, 0, TRUE },
{ "fix_thumbs", fix_thumbs, 1, TRUE },
{ "annotate_string", annotate_string, 1, FALSE },
{ "preset", preset_cmd, 1, FALSE },
{ "servo", servo_cmd, 1, FALSE },
{ "upgrade", upgrade, 0, TRUE },
{ "quit", quit, 0, TRUE },
};
@ -901,6 +907,7 @@ command_process(char *command_line)
time_lapse.period = n;
config_timelapse_save_status();
config_set_boolean(&time_lapse.show_status, "on");
pikrellcam.state_modified = TRUE;
break;
case tl_hold:
@ -935,6 +942,7 @@ command_process(char *command_line)
exec_no_wait(pikrellcam.timelapse_convert_cmd, NULL);
config_set_boolean(&time_lapse.show_status, "on");
pikrellcam.state_modified = TRUE;
display_inform("\"Timelapse ended.\" 3 3 1");
display_inform("\"Starting convert...\" 4 3 1");
display_inform("timeout 2");
@ -971,6 +979,7 @@ command_process(char *command_line)
case tl_show_status:
config_set_boolean(&time_lapse.show_status, args);
pikrellcam.state_modified = TRUE;
break;
case display_cmd:
@ -1095,6 +1104,14 @@ command_process(char *command_line)
log_printf("Wrong number of args for command: %s\n", command);
break;
case preset_cmd:
preset_command(args);
break;
case servo_cmd:
servo_command(args);
break;
case upgrade:
snprintf(buf, sizeof(buf), "%s/scripts-dist/_upgrade $I $P $G $Z",
pikrellcam.install_dir);
@ -1103,8 +1120,11 @@ command_process(char *command_line)
case quit:
config_timelapse_save_status();
preset_state_save();
if (pikrellcam.config_modified)
config_save(pikrellcam.config_file);
if (pikrellcam.preset_modified)
preset_config_save();
display_quit();
exit(0);
break;
@ -1185,49 +1205,112 @@ static void
signal_quit(int sig)
{
config_timelapse_save_status();
preset_state_save();
if (pikrellcam.config_modified)
config_save(pikrellcam.config_file);
if (pikrellcam.preset_modified)
preset_config_save();
display_quit();
log_printf("quit signal received - exiting!\n");
exit(0);
}
static void
log_start(boolean start_sep, boolean time, boolean end_sep)
{
char buf[100];
if (start_sep)
log_printf_no_timestamp("\n========================================================\n");
if (time)
{
strftime(buf, sizeof(buf), "%F %T", localtime(&pikrellcam.t_now));
log_printf_no_timestamp("======= PiKrellCam %s started at %s\n",
pikrellcam.version, buf);
}
if (end_sep)
log_printf_no_timestamp("========================================================\n");
}
int
main(int argc, char *argv[])
{
int fifo;
int i, n;
char *opt, *arg, *equal_arg, *homedir, *user;
char *opt, *arg, *equal_arg, *user;
char *line, *eol, buf[4096];
pgm_name = argv[0];
bcm_host_init();
setlocale(LC_TIME, "");
time(&pikrellcam.t_now);
config_set_defaults();
for (i = 1; i < argc; i++)
get_arg_pass1(argv[i]);
config_set_defaults(homedir);
if (!config_load(pikrellcam.config_file))
config_save(pikrellcam.config_file);
if (!motion_regions_config_load(pikrellcam.motion_regions_config_file, FALSE))
motion_regions_config_save(pikrellcam.motion_regions_config_file, FALSE);
if (quit_flag) /* Just making sure initial config file is written */
exit(0);
if (*pikrellcam.log_file != '/')
if (!pikrellcam.log_file || !*pikrellcam.log_file)
pikrellcam.log_file = strdup("/dev/null");
else if (*pikrellcam.log_file != '/')
{
snprintf(buf, sizeof(buf), "%s/%s", pikrellcam.install_dir, pikrellcam.log_file);
dup_string(&pikrellcam.log_file, buf);
}
if (!quit_flag)
/* If need to mmap() gpios for servos, restart a sudo pikrellcam which can
| mmap() /dev/mem and then drop priviledes back to orig user/group
*/
if (getuid() == 0) /* root, so mmap(), drop privileges and continue */
{
log_printf_no_timestamp("\n========================================================\n");
strftime(buf, sizeof(buf), "%F %T", localtime(&pikrellcam.t_now));
log_printf_no_timestamp("%s ===== PiKrellCam %s started =====\n", buf, pikrellcam.version);
log_printf_no_timestamp("========================================================\n");
log_start(FALSE, TRUE, FALSE);
servo_init();
if (user_gid > 0)
setgid(user_gid);
setuid(user_uid);
log_printf_no_timestamp("== Dropped root priviledges-continuing as normal user ==\n");
log_start(FALSE, FALSE, TRUE);
}
else if (pikrellcam.have_servos && !pikrellcam.servo_use_servoblaster)
{
/* Need to restart pikrellcam as root so can mmap() PWMs for servos.
*/
log_start(TRUE, FALSE, FALSE);
log_printf_no_timestamp("========= Restarting as root to mmap() servos ==========\n");
homedir = getpwuid(geteuid())->pw_dir;
i = snprintf(buf, sizeof(buf), "sudo %s -user%d -group%d -home%s ",
*argv++, (int) getuid(), (int) getgid(), homedir);
while (--argc && i < sizeof(buf) - 64 - strlen(*argv))
i = sprintf(buf + i, "%s ", *argv++);
set_exec_with_session(FALSE);
exec_wait(buf, NULL); /* restart as root so can mmap() gpios*/
exit(0);
}
else if (pikrellcam.servo_use_servoblaster)
{
log_start(TRUE, TRUE,FALSE);
servo_init();
log_start(FALSE, FALSE, TRUE);
}
else
log_start(TRUE, TRUE, TRUE);
bcm_host_init();
if (!homedir)
homedir = getpwuid(geteuid())->pw_dir;
user = strrchr(homedir, '/');
pikrellcam.effective_user = strdup(user ? user + 1 : "pi");
if (!motion_regions_config_load(pikrellcam.motion_regions_config_file, FALSE))
motion_regions_config_save(pikrellcam.motion_regions_config_file, FALSE);
preset_config_load();
if (!at_commands_config_load(pikrellcam.at_commands_config_file))
at_commands_config_save(pikrellcam.at_commands_config_file);
@ -1238,11 +1321,6 @@ main(int argc, char *argv[])
continue;
opt = argv[i];
/* Just for initial install-pikrellcam.sh run to create config files.
*/
if (!strcmp(opt, "-quit"))
exit(0);
/* Accept: --opt arg -opt arg opt=arg --opt=arg -opt=arg
*/
for (i = 0; i < 2; ++i)
@ -1272,10 +1350,6 @@ main(int argc, char *argv[])
if (pikrellcam.debug)
printf("debugging...\n");
homedir = getpwuid(geteuid())->pw_dir;
user = strrchr(homedir, '/');
pikrellcam.effective_user = strdup(user ? user + 1 : "pi");
if (*pikrellcam.media_dir != '/')
{
snprintf(buf, sizeof(buf), "%s/%s", pikrellcam.install_dir, pikrellcam.media_dir);
@ -1313,8 +1387,9 @@ main(int argc, char *argv[])
)
exit(1);
snprintf(buf, sizeof(buf), "%s/scripts-dist/_init $I $a $m $M $P $G",
pikrellcam.install_dir);
snprintf(buf, sizeof(buf), "%s/scripts-dist/_init $I $a $m $M $P $G %s",
pikrellcam.install_dir,
(pikrellcam.have_servos) ? "servos_on" : "servos_off");
exec_wait(buf, NULL);
/* User may have enabled a mount disk on media_dir
@ -1344,6 +1419,7 @@ main(int argc, char *argv[])
camera_start();
config_timelapse_load_status();
preset_state_load();
pikrellcam.state_modified = TRUE;
signal(SIGINT, signal_quit);

View File

@ -1,6 +1,6 @@
/* PiKrellCam
|
| Copyright (C) 2015 Bill Wilson billw@gkrellm.net
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
|
| PiKrellCam is free software: you can redistribute it and/or modify it
| under the terms of the GNU General Public License as published by
@ -50,7 +50,7 @@
#include "utils.h"
#define PIKRELLCAM_VERSION "2.1.13"
#define PIKRELLCAM_VERSION "3.0.0"
//TCP Stream Server
@ -89,6 +89,9 @@ extern int h264_conn_status;
#define PIKRELLCAM_STILL_SUBDIR "stills"
#define PIKRELLCAM_TIMELAPSE_SUBDIR "timelapse"
#define SERVO_MIN_WIDTH 50
#define SERVO_MAX_WIDTH 250
/* ------------------ MMAL Camera ---------------
*/
@ -275,7 +278,7 @@ typedef struct
int motion_status;
boolean motion_enable,
show_vectors,
show_regions;
show_preset;
boolean do_preview_save,
do_preview_save_cmd;
@ -427,6 +430,26 @@ typedef struct
extern CameraAdjust camera_adjust_temp;
typedef struct
{
int pan,
tilt,
settings_index,
n_settings;
SList *settings_list;
}
PresetPosition;
typedef struct
{
int mag_limit,
mag_limit_count,
burst_count,
burst_frames;
SList *region_list; /* string "xf0 yf0 dxf dyf" */
}
PresetSettings;
typedef struct
{
@ -491,7 +514,8 @@ typedef struct
*on_motion_preview_save_cmd;
boolean motion_preview_clean,
motion_vertical_filter,
motion_stats;
motion_stats,
motion_show_counts;
int motion_area_min_side;
CameraConfig
@ -533,8 +557,38 @@ typedef struct
*timelapse_format,
*timelapse_jpeg_last,
*timelapse_status_file;
boolean timelapse_capture_event;
char *timelapse_convert_cmd;
int servo_pan_gpio,
servo_tilt_gpio,
servo_pan_min,
servo_pan_max,
servo_tilt_min,
servo_tilt_max,
servo_preset_step_msec,
servo_move_step_msec,
servo_move_steps,
servo_settle_msec;
boolean servo_pan_invert,
servo_tilt_invert,
servo_moving,
servo_use_servoblaster,
have_servos;
SList *preset_position_list;
int preset_position_index,
n_preset_positions;
char *preset_config_file,
*preset_state_file;
PresetPosition
*preset_last_on;
boolean preset_modified,
preset_modified_warning,
preset_notify,
motion_off_preset,
on_preset; /* modify in servo_control.mutex */
char *annotate_format_string,
annotate_string_space_char;
SList *annotate_list;
@ -686,7 +740,7 @@ CameraParameter
extern boolean config_load(char *config_file);
extern void config_save(char *config_file);
extern void config_set_defaults(void);
extern void config_set_defaults(char *homedir);
extern boolean config_set_option(char *opt, char *arg, boolean set_safe);
boolean config_boolean_value(char *value);
void config_set_boolean(boolean *result, char *arg);
@ -735,6 +789,8 @@ Event *event_count_down_add(char *name, int count,
void (*func)(), void *data);
Event *event_find(char *name);
void event_list_lock(void);
void event_list_unlock(void);
void event_remove(Event *event);
void event_process(void);
void event_preview_save(void);
@ -750,6 +806,32 @@ int exec_wait(char *command, char *arg);
void exec_no_wait(char *command, char *arg);
Event *exec_child_event(char *event_name, char *command, char *arg);
void preset_command(char *args);
void preset_config_load(void);
void preset_config_save(void);
void preset_state_save(void);
void preset_state_load(void);
void preset_load_values(boolean do_pan);
void preset_on_check(int pan, int tilt);
PresetPosition *preset_find_at_position(int pan, int tilt);
void preset_settings_set_modified(void);
void preset_regions_set_modified(void);
void preset_pan_range(int *max, int *min);
void preset_tilt_range(int *max, int *min);
void gpio_alt_function(int pin, int altfn);
void gpio_set_mode(int pin, int mode);
void gpio_set_pud(int pin, int pud);
void gpio_to_channel(int gpio, int *channel, int *altfn);
int gpio_read(int pin);
void gpio_write(int pin, int level);
void gpio_hardware_pwm(int pin);
void servo_get_position(int *pan, int *tilt);
void servo_move(int pan, int tilt, int delay);
void servo_command(char *args);
void servo_init(void);
void set_exec_with_session(boolean set);
void sun_times_init(void);
void at_commands_config_save(char *config_file);
boolean at_commands_config_load(char *config_file);

900
src/preset.c Normal file
View File

@ -0,0 +1,900 @@
/* PiKrellCam
|
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
|
| PiKrellCam 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 3 of the License, or
| (at your option) any later version.
|
| PiKrellCam 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, see http://www.gnu.org/licenses/
|
| This file is part of PiKrellCam.
*/
#include "pikrellcam.h"
#define PREV_POSITION 0
#define NEXT_POSITION 1
#define PREV_SETTINGS 2
#define NEXT_SETTINGS 3
#define GOTO 4
#define PRESET_NEW 5
#define PRESET_COPY 6
#define PRESET_DELETE 7
#define MOVE_ONE 8
#define MOVE_ALL 9
typedef struct
{
char *name;
int id,
n_args;
}
PresetCommand;
static PresetCommand preset_commands[] =
{
{ "prev_position", PREV_POSITION, 0 },
{ "next_position", NEXT_POSITION, 0 },
{ "prev_settings", PREV_SETTINGS, 0 },
{ "next_settings", NEXT_SETTINGS, 0 },
{ "goto", GOTO, 1 },
{ "move_one", MOVE_ONE, 0 },
{ "move_all", MOVE_ALL, 0 },
{ "new", PRESET_NEW, 0 },
{ "copy", PRESET_COPY, 0 },
{ "delete", PRESET_DELETE, 0 }
};
#define N_PRESET_COMMANDS (sizeof(preset_commands) / sizeof(PresetCommand))
static void
preset_notify(int count)
{
Event *event;
event_list_lock();
if ((event = event_find("preset notify")) != NULL)
event->count = count; /* extend time of existing notify */
event_list_unlock();
if (!event)
event_count_down_add("preset notify", count,
event_notify_expire, &pikrellcam.preset_notify);
pikrellcam.preset_notify = TRUE;
}
static int
pan_position_cmp(void *data1, void *data2)
{
PresetPosition *pos1 = (PresetPosition *) data1,
*pos2 = (PresetPosition *) data2;
if (pos1->pan > pos2->pan)
return 1;
return 0;
}
PresetPosition *
preset_find_at_position(int pan, int tilt)
{
PresetPosition *pos = NULL;
SList *list;
int index = 0;
for (list = pikrellcam.preset_position_list; list; list = list->next)
{
pos = (PresetPosition *) list->data;
if (pos->pan == pan && (tilt == 0 || tilt == pos->tilt))
{
pikrellcam.preset_position_index = index;
break;
}
++index;
pos = NULL;
}
return pos;
}
/* Called from locked servo_control.mutex in servo_thread()
*/
void
preset_on_check(int pan, int tilt)
{
PresetPosition *pos;
if ((pos = preset_find_at_position(pan, tilt)) != NULL)
{
pikrellcam.on_preset = TRUE;
pikrellcam.preset_last_on = pos;
preset_load_values(FALSE);
preset_notify(22);
}
}
void
preset_pan_range(int *max, int *min)
{
PresetPosition *pos = NULL;
SList *list;
*max = SERVO_MIN_WIDTH;
*min = SERVO_MAX_WIDTH;
for (list = pikrellcam.preset_position_list; list; list = list->next)
{
pos = (PresetPosition *) list->data;
if (pos->pan > *max)
*max = pos->pan;
if (pos->pan < *min)
*min = pos->pan;
}
}
void
preset_tilt_range(int *max, int *min)
{
PresetPosition *pos = NULL;
SList *list;
*max = SERVO_MIN_WIDTH;
*min = SERVO_MAX_WIDTH;
for (list = pikrellcam.preset_position_list; list; list = list->next)
{
pos = (PresetPosition *) list->data;
if (pos->tilt > *max)
*max = pos->tilt;
if (pos->tilt < *min)
*min = pos->tilt;
}
}
static int
preset_position_prev(void)
{
PresetPosition *pos;
SList *list;
int idx, pan, tilt;
servo_get_position(&pan, &tilt);
idx = -1;
for (list = pikrellcam.preset_position_list; list; list = list->next)
{
pos = (PresetPosition *) list->data;
if (pos->pan >= pan)
{
if (pos->pan == pan && pos->tilt != tilt)
++idx; /* stay on same pan position */
break;
}
++idx;
}
if (idx >= pikrellcam.n_preset_positions)
idx = pikrellcam.n_preset_positions - 1;
if (idx == -1)
idx = 0;
return idx;
}
static int
preset_position_next(void)
{
PresetPosition *pos;
SList *list;
int idx, pan, tilt;
servo_get_position(&pan, &tilt);
idx = 0;
for (list = pikrellcam.preset_position_list; list; list = list->next)
{
pos = (PresetPosition *) list->data;
if ( (pos->pan == pan && pos->tilt != tilt)
|| pos->pan > pan
)
break;
++idx;
}
if (idx >= pikrellcam.n_preset_positions)
idx = pikrellcam.n_preset_positions - 1;
return idx;
}
void
preset_load_values(boolean do_pan)
{
PresetPosition *pos;
PresetSettings *settings;
Event *event;
MotionFrame *mf = &motion_frame;
SList *rlist;
char *region, buf[100];
boolean save_show;
pos = (PresetPosition *) slist_nth_data(pikrellcam.preset_position_list,
pikrellcam.preset_position_index);
if (!pos)
return;
//printf("preset_load %d %d (%d %d) servo:%d %d\n",
//pikrellcam.have_servos, do_pan,
//pikrellcam.preset_position_index, pos->settings_index, pos->pan, pos->tilt);
if (pikrellcam.have_servos && do_pan && pos->pan > 0)
{
event_list_lock();
if ((event = event_find("preset notify")) != NULL)
if (event->count > 0)
event->count = 1;
event_list_unlock();
servo_move(pos->pan, pos->tilt, pikrellcam.servo_preset_step_msec);
/* preset_load_values(FALSE) will be called again after move
| if the move ends up on a preset
*/
return;
}
if (!pikrellcam.have_servos)
{
pikrellcam.on_preset = TRUE;
pikrellcam.preset_last_on = pos;
}
settings = (PresetSettings *) slist_nth_data(pos->settings_list, pos->settings_index);
if (settings)
{
pikrellcam.motion_magnitude_limit = settings->mag_limit;
pikrellcam.motion_magnitude_limit_count = settings->mag_limit_count;
pikrellcam.motion_burst_count = settings->burst_count;
pikrellcam.motion_burst_frames = settings->burst_frames;
}
save_show = mf->show_preset;
motion_command("delete_regions all");
for (rlist = settings->region_list; rlist; rlist = rlist->next)
{
region = (char *) rlist->data;
snprintf(buf, sizeof(buf), "%s", region);
motion_command(buf);
}
mf->show_preset = save_show;
pikrellcam.preset_modified_warning = FALSE;
pikrellcam.state_modified = TRUE;
}
static void
preset_settings_regions_set(PresetSettings *settings)
{
MotionFrame *mf = &motion_frame;
MotionRegion *mreg;
SList *list;
char *region;
if (!settings)
return;
slist_and_data_free(settings->region_list);
settings->region_list = NULL;
for (list = mf->motion_region_list; list; list = list->next)
{
mreg = (MotionRegion *) list->data;
asprintf(&region, "add_region %.3f %.3f %.3f %.3f\n",
mreg->xf0, mreg->yf0, mreg->dxf, mreg->dyf);
settings->region_list = slist_append(settings->region_list, region);
}
}
void
preset_settings_set_modified(void)
{
PresetPosition *pos = NULL;
PresetSettings *settings = NULL;
int pan, tilt;
servo_get_position(&pan, &tilt);
if ((pos = preset_find_at_position(pan, tilt)) != NULL)
{
settings = (PresetSettings *) slist_nth_data(pos->settings_list,
pos->settings_index);
if (settings)
{
settings->mag_limit = pikrellcam.motion_magnitude_limit;
settings->mag_limit_count = pikrellcam.motion_magnitude_limit_count;
settings->burst_count = pikrellcam.motion_burst_count;
settings->burst_frames = pikrellcam.motion_burst_frames;
pikrellcam.preset_modified = TRUE;
}
}
else
pikrellcam.preset_modified_warning = TRUE;
}
/* Called from locked mf->region_list_mutex when a region is modified.
*/
void
preset_regions_set_modified(void)
{
PresetPosition *pos = NULL;
PresetSettings *settings = NULL;
int pan, tilt;
servo_get_position(&pan, &tilt);
if ((pos = preset_find_at_position(pan, tilt)) != NULL)
{
settings = (PresetSettings *) slist_nth_data(pos->settings_list,
pos->settings_index);
preset_settings_regions_set(settings);
pikrellcam.preset_modified = TRUE;
}
else
pikrellcam.preset_modified_warning = TRUE;
}
static void
preset_new(PresetPosition *pos_src)
{
PresetPosition *pos = NULL;
PresetSettings *settings = NULL, *settings_src;
SList *slist, *rlist;
char *region;
int pan, tilt;
servo_get_position(&pan, &tilt);
if (pikrellcam.preset_position_list)
{
pos = (PresetPosition *) slist_nth_data(pikrellcam.preset_position_list,
pikrellcam.preset_position_index);
if ( pikrellcam.have_servos
&& (pos->pan != pan || pos->tilt != tilt)
)
pos = NULL;
}
if (!pos)
{
pos = calloc(1, sizeof(PresetPosition));
pos->pan = pan;
pos->tilt = tilt;
pos->n_settings = 0;
pos->settings_index = -1;
pikrellcam.preset_position_list =
slist_insert_sorted(pikrellcam.preset_position_list, pos,
pan_position_cmp);
pikrellcam.n_preset_positions += 1;
pikrellcam.preset_position_index =
slist_index(pikrellcam.preset_position_list, pos);
if (pikrellcam.preset_position_index == -1)
pikrellcam.preset_position_index = 0;
}
else
pos_src = NULL;
if (pos_src)
{
for (slist = pos_src->settings_list; slist; slist = slist->next)
{
settings_src = (PresetSettings *) slist->data;
settings = calloc(1, sizeof(PresetSettings));
settings->mag_limit = settings_src->mag_limit;
settings->mag_limit_count = settings_src->mag_limit_count;
settings->burst_count = settings_src->burst_count;
settings->burst_frames = settings_src->burst_frames;
for (rlist = settings_src->region_list; rlist; rlist = rlist->next)
{
region = strdup((char *) rlist->data);
settings->region_list = slist_append(settings->region_list, region);
}
pos->settings_list = slist_append(pos->settings_list, settings);
pos->n_settings += 1;
}
pos->settings_index = pos_src->settings_index;
}
else
{
settings = calloc(1, sizeof(PresetSettings));
pos->settings_index += 1;
pos->settings_list = slist_insert(pos->settings_list,
settings, pos->settings_index);
pos->n_settings += 1;
settings->mag_limit = pikrellcam.motion_magnitude_limit;
settings->mag_limit_count = pikrellcam.motion_magnitude_limit_count;
settings->burst_count = pikrellcam.motion_burst_count;
settings->burst_frames = pikrellcam.motion_burst_frames;
preset_settings_regions_set(settings);
preset_notify(18);
}
pikrellcam.preset_modified = TRUE;
pikrellcam.preset_modified_warning = FALSE;
pikrellcam.on_preset = TRUE;
pikrellcam.preset_last_on = pos;
}
static void
preset_delete(void)
{
PresetPosition *pos = NULL;
PresetSettings *settings = NULL;
int pan, tilt, idx;
char buf[100];
if (!pikrellcam.preset_position_list)
return;
if (pikrellcam.have_servos)
{
servo_get_position(&pan, &tilt);
if ((pos = preset_find_at_position(pan, tilt)) == NULL)
{
display_inform("\"Position is not on a preset.\" 3 3 1");
display_inform("\"Cannot delete.\" 4 3 1");
display_inform("timeout 3");
return;
}
}
else
pos = (PresetPosition *) slist_nth_data(pikrellcam.preset_position_list,
pikrellcam.preset_position_index);
if (pikrellcam.n_preset_positions == 1 && pos->n_settings == 1)
{
display_inform("\"You must have at least one preset.\" 3 3 1");
display_inform("\"Cannot delete your only preset.\" 4 3 1");
display_inform("timeout 3");
return;
}
settings = (PresetSettings *) slist_nth_data(pos->settings_list, pos->settings_index);
slist_and_data_free(settings->region_list);
pos->settings_list = slist_remove(pos->settings_list, settings);
free(settings);
pos->n_settings -= 1;
if ((idx = pos->settings_index) >= pos->n_settings)
pos->settings_index -= 1;
if (pos->n_settings <= 0)
{
display_inform("\"Deleted preset at current position.\" 3 3 1");
display_inform("timeout 2");
pikrellcam.preset_position_list = slist_remove(pikrellcam.preset_position_list, pos);
pikrellcam.n_preset_positions -= 1;
pikrellcam.on_preset = FALSE;
pikrellcam.preset_last_on = NULL;
if (pikrellcam.preset_position_index > 0)
pikrellcam.preset_position_index -= 1;
}
else
{
snprintf(buf, sizeof(buf), "\"Deleted preset settings %d.\" 3 3 1", idx + 1);
display_inform(buf);
display_inform("timeout 2");
}
preset_load_values(FALSE);
preset_notify(18);
pikrellcam.preset_modified = TRUE;
}
void
preset_command(char *cmd_line)
{
PresetCommand *pcmd;
PresetPosition *pos;
SList *list;
int i, n, id = -1;
int pan, tilt, dpan, dtilt;
char buf[64], arg1[32];
boolean move_all = FALSE;
arg1[0] = '\0';
n = sscanf(cmd_line, "%63s %[^\n]", buf, arg1);
if (n < 1)
return;
for (i = 0; i < N_PRESET_COMMANDS; ++i)
{
pcmd = &preset_commands[i];
if (!strcmp(pcmd->name, buf))
{
if (pcmd->n_args <= n - 1)
id = pcmd->id;
break;
}
}
if (id == -1)
{
// inform_message("Bad preset command.");
return;
}
servo_get_position(&pan, &tilt);
switch (id)
{
case PREV_POSITION:
pikrellcam.preset_position_index = preset_position_prev();
preset_load_values(TRUE);
break;
case NEXT_POSITION:
pikrellcam.preset_position_index = preset_position_next();
preset_load_values(TRUE);
break;
case PREV_SETTINGS:
pos = (PresetPosition *) slist_nth_data(pikrellcam.preset_position_list, pikrellcam.preset_position_index);
if (pos)
{
if (pos->tilt == tilt && pos->pan == pan)
{
if (pos->settings_index > 0)
{
--pos->settings_index;
preset_load_values(FALSE);
preset_notify(18);
}
else
preset_notify(10);
}
else
preset_load_values(TRUE);
}
break;
case NEXT_SETTINGS:
pos = (PresetPosition *) slist_nth_data(pikrellcam.preset_position_list, pikrellcam.preset_position_index);
if (pos)
{
if (pos->tilt == tilt && pos->pan == pan)
{
n = slist_length(pos->settings_list);
if (pos->settings_index < n - 1)
{
++pos->settings_index;
preset_load_values(FALSE);
preset_notify(18);
}
else
preset_notify(10);
}
else
preset_load_values(TRUE);
}
break;
case MOVE_ALL:
move_all = TRUE;
case MOVE_ONE:
if (pikrellcam.on_preset || !pikrellcam.preset_last_on)
{
if (pikrellcam.on_preset)
display_inform("\"Servo position is on a preset.\" 2 3 1");
else
display_inform("\"Move to a preset first, then off.\" 2 3 1");
display_inform("\"Servos must be moved off a preset\" 3 3 1");
display_inform("\"before the preset can be moved.\" 4 3 1");
display_inform("timeout 3");
}
else if (pikrellcam.preset_last_on)
{
dpan = pan - pikrellcam.preset_last_on->pan;
dtilt = tilt - pikrellcam.preset_last_on->tilt;
for (list = pikrellcam.preset_position_list; list; list = list->next)
{
pos = (PresetPosition *) list->data;
if (move_all || pos == pikrellcam.preset_last_on)
{
if ( pos->pan + dpan > pikrellcam.servo_pan_max
|| pos->pan + dpan < pikrellcam.servo_pan_min
|| pos->tilt + dtilt > pikrellcam.servo_tilt_max
|| pos->tilt + dtilt < pikrellcam.servo_tilt_min
)
{
display_inform("\"Error:\" 2 3 1");
display_inform("\"Can't move a preset past\" 3 3 1");
display_inform("\"servo pan/tilt limits.\" 4 3 1");
display_inform("timeout 3");
dpan = dtilt = 0;
break;
}
}
}
if (move_all)
{
for (list = pikrellcam.preset_position_list; list; list = list->next)
{
pos = (PresetPosition *) list->data;
pos->pan += dpan;
pos->tilt += dtilt;
}
}
else
{
pos = pikrellcam.preset_last_on;
pos->pan += dpan;
pos->tilt += dtilt;
/* Re insert sorted in case moved past another preset.
*/
pikrellcam.preset_position_list =
slist_remove(pikrellcam.preset_position_list, pos);
pikrellcam.preset_position_list =
slist_insert_sorted(pikrellcam.preset_position_list,
pos, pan_position_cmp);
}
if (dpan != 0 || dtilt != 0)
{
preset_on_check(pan, tilt);
pikrellcam.preset_modified = TRUE;
}
}
break;
case GOTO:
if (sscanf(arg1, "%d %d", &n, &i) == 2)
{
if (--n < 0) /* n was 1 based position number */
n = pikrellcam.preset_position_index;
pos = (PresetPosition *) slist_nth_data(pikrellcam.preset_position_list, n);
if (pos)
{
pikrellcam.preset_position_index = n;
--i; /* settings number was 1 based */
if (i < 0 || i >= slist_length(pos->settings_list))
i = 0;
pos->settings_index = i;
preset_load_values(TRUE);
preset_notify(18);
}
}
break;
case PRESET_NEW:
pos = preset_find_at_position(pan, 0);
if (pos)
{
if (pos->tilt == tilt)
{
preset_new(NULL);
display_inform("\"Created new settings at current preset\" 3 3 1");
display_inform("timeout 2");
}
else
{
display_inform("\"Pan position is on a preset but tilt\" 2 3 1");
display_inform("\"is not. Cannot create new settings\" 3 3 1");
display_inform("\"for this preset.\" 4 3 1");
display_inform("timeout 3");
}
}
else
{
preset_new(NULL);
display_inform("\"Created preset at new position.\" 3 3 1");
display_inform("timeout 2");
}
break;
case PRESET_COPY:
pos = preset_find_at_position(pan, 0);
if (pos || !pikrellcam.preset_last_on)
{
if (pos)
display_inform("\"Pan position is on a preset.\" 2 3 1");
else
display_inform("\"Move to a preset first, then off.\" 2 3 1");
display_inform("\"Pan servo must be moved off a\" 3 3 1");
display_inform("\"preset before you can copy it.\" 4 3 1");
display_inform("timeout 3");
}
else
{
preset_new(pikrellcam.preset_last_on);
display_inform("\"Copied preset into new position.\" 3 3 1");
display_inform("timeout 2");
}
break;
case PRESET_DELETE:
preset_delete();
break;
}
}
void
preset_config_load(void)
{
FILE *f;
PresetPosition *pos = NULL;
PresetSettings *settings = NULL;
char *region;
int pan, tilt;
char buf[100];
if ((f = fopen(pikrellcam.preset_config_file, "r")) == NULL)
{
preset_new(NULL);
preset_config_save();
return;
}
while (fgets(buf, sizeof(buf), f) != NULL)
{
if (buf[0] == '#' || buf[0] == '\n')
continue;
if (sscanf(buf, "<position %d %d>", &pan, &tilt) == 2)
{
pos = calloc(1, sizeof(PresetPosition));
pos->pan = pan;
pos->tilt = tilt;
pikrellcam.preset_position_list = slist_append(pikrellcam.preset_position_list, pos);
pikrellcam.n_preset_positions += 1;
continue;
}
if (!pos)
continue;
if (!strncmp(buf, "<settings>", 10))
{
settings = calloc(1, sizeof(PresetSettings));
pos->settings_list = slist_append(pos->settings_list, settings);
pos->n_settings += 1;
continue;
}
if (!settings)
continue;
if (!strncmp(buf, "motion", 6))
sscanf(buf + 7, "%d %d %d %d",
&settings->mag_limit, &settings->mag_limit_count,
&settings->burst_count, &settings->burst_frames);
if (!strncmp(buf, "magnitude_limit", 15))
sscanf(buf + 16, "%d", &settings->mag_limit);
else if (!strncmp(buf, "magnitude_count", 15))
sscanf(buf + 16, "%d", &settings->mag_limit_count);
else if (!strncmp(buf, "burst_count", 11))
sscanf(buf + 12, "%d", &settings->burst_count);
else if (!strncmp(buf, "burst_frames", 12))
sscanf(buf + 13, "%d", &settings->burst_frames);
else if (!strncmp(buf, "add_region", 10))
{
region = strdup(buf); /* string "add_region xf0 yf0 dxf dyf" */
settings->region_list = slist_append(settings->region_list, region);
}
}
fclose(f);
/* read preset state file to load position_index, settings_index */
}
void
preset_config_save(void)
{
FILE *f;
SList *list, *slist, *rlist;
PresetPosition *pos;
PresetSettings *settings;
char *region;
if ((f = fopen(pikrellcam.preset_config_file, "w")) == NULL)
{
log_printf("Failed to save preset config file %s. %m\n",
pikrellcam.preset_config_file);
return;
}
for (list = pikrellcam.preset_position_list; list; list = list->next)
{
pos = (PresetPosition *) list->data;
fprintf(f, "<position %d %d>\n", pos->pan, pos->tilt);
for (slist = pos->settings_list; slist; slist = slist->next)
{
settings = (PresetSettings *) slist->data;
fprintf(f, "<settings>\n");
fprintf(f, "magnitude_limit %d\n", settings->mag_limit);
fprintf(f, "magnitude_count %d\n", settings->mag_limit_count);
fprintf(f, "burst_count %d\n", settings->burst_count);
fprintf(f, "burst_frames %d\n", settings->burst_frames);
for (rlist = settings->region_list; rlist; rlist = rlist->next)
{
region = (char *) rlist->data; /* string "add_region xf0 yf0 dxf dyf" */
fprintf(f, "%s", region);
}
}
fprintf(f, "\n");
}
fclose(f);
pikrellcam.preset_modified = FALSE;
}
void
preset_state_save(void)
{
FILE *f;
PresetPosition *pos;
SList *list;
int pan, tilt;
f = fopen(pikrellcam.preset_state_file, "w");
if (f)
{
servo_get_position(&pan, &tilt);
fprintf(f, "pan %d\n", pan);
fprintf(f, "tilt %d\n", tilt);
fprintf(f, "position_index %d\n", pikrellcam.preset_position_index);
for (list = pikrellcam.preset_position_list; list; list = list->next)
{
pos = (PresetPosition *) list->data;
fprintf(f, "settings_index %d\n", pos->settings_index);
}
fclose(f);
}
}
void
preset_state_load(void)
{
FILE *f;
SList *list = pikrellcam.preset_position_list;
PresetPosition *pos;
int pan = 150, tilt = 150, i;
char buf[100];
if ((f = fopen(pikrellcam.preset_state_file, "r")) == NULL)
{
preset_load_values(TRUE);
return;
}
while (fgets(buf, sizeof(buf), f) != NULL)
{
if (buf[0] == '#' || buf[0] == '\n')
continue;
/* XXX */
if (!strncmp(buf, "position_index settings_index pan tilt", 38))
{
fgets(buf, sizeof(buf), f);
sscanf(buf, "%d %d %d %d\n", &pikrellcam.preset_position_index,
&i, &pan, &tilt);
break;
}
/* XXX */
if (!strncmp(buf, "pan", 3))
sscanf(buf + 4, "%d", &pan);
else if (!strncmp(buf, "tilt", 4))
sscanf(buf + 5, "%d", &tilt);
else if (!strncmp(buf, "position_index", 14))
sscanf(buf + 15, "%d", &pikrellcam.preset_position_index);
else if (!strncmp(buf, "settings_index", 14) && list)
{
pos = (PresetPosition *) list->data;
sscanf(buf + 15, "%d", &pos->settings_index);
if ( pos->settings_index < 0
|| pos->settings_index > slist_length(pos->settings_list) - 1
)
pos->settings_index = 0;
list = list->next;
}
}
fclose(f);
if ( pikrellcam.preset_position_index < 0
|| pikrellcam.preset_position_index > pikrellcam.n_preset_positions - 1
)
pikrellcam.preset_position_index = 0;
if (pan < pikrellcam.servo_pan_min || pan > pikrellcam.servo_pan_max)
pan = 150;
if (tilt < pikrellcam.servo_tilt_min || tilt > pikrellcam.servo_tilt_max)
tilt = 150;
/* Don't move to the preset position because we may not have been on
| a preset when stopped. Just move to saved pan/tilt.
*/
preset_load_values(FALSE);
if (pikrellcam.have_servos)
servo_move(pan, tilt, pikrellcam.servo_move_step_msec);
else
preset_notify(22);
}

637
src/servo.c Normal file
View File

@ -0,0 +1,637 @@
/* PiKrellCam
|
| Copyright (C) 2015-2016 Bill Wilson billw@gkrellm.net
|
| PiKrellCam 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 3 of the License, or
| (at your option) any later version.
|
| PiKrellCam 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, see http://www.gnu.org/licenses/
|
| This file is part of PiKrellCam.
*/
/* BCM2835-ARM-Peripherals.pdf document:
| https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf
| Addendum covers clock manager:
| http://www.scribd.com/doc/127599939/BCM2835-Audio-clocks
*/
#include "pikrellcam.h"
#include <stdint.h>
#include <sys/mman.h>
#define PI_1_PERIPHERAL_BASE 0x20000000
#define PI_2_PERIPHERAL_BASE 0x3F000000
#define GPIO_BASE 0x200000
#define PWM_BASE 0x20C000
#define CLOCK_BASE 0x101000
/* xxx_REG defines are uint32_t pointer offsets to registers in the gpio,
| pwm or clock address space and are BCM2835-ARM-Peripherals.pdf or scribd
| register byte addresses / 4
*/
#define PWM_CTL_REG (0x0 / 4)
#define CTL_REG_RESET_STATE 0
#define CTL_REG_PWM1_ENABLE 1
#define CTL_REG_MSEN1 0x80
#define CTL_REG_PWM1_MS_MODE (CTL_REG_PWM1_ENABLE | CTL_REG_MSEN1)
#define CTL_REG_PWM2_ENABLE 0x100
#define CTL_REG_MSEN2 0x8000
#define CTL_REG_PWM2_MS_MODE (CTL_REG_PWM2_ENABLE | CTL_REG_MSEN2)
#define PWM_RNG1_REG (0x10 / 4)
#define PWM_DAT1_REG (0x14 / 4)
#define PWM_RNG2_REG (0x20 / 4)
#define PWM_DAT2_REG (0x24 / 4)
#define GPSET_REG (0x1c / 4)
#define GPCLR_REG (0x28 / 4)
#define GPLEV_REG (0x34 / 4)
#define GPPUD_REG (0x94 / 4)
#define PUD_DOWN 1
#define PUD_UP 2
#define GPPUDCLK_REG (0x98 / 4)
/* PWM clock manager registers CM_PWMDIV & CM_PWMCTL from the scribd addendum:
*/
#define CM_PASSWORD 0x5A000000
#define CM_PWMCTL_REG (0xa0 / 4)
#define PWMCTL_BUSY 0x80 // Read only
#define PWMCTL_KILL 0x20
#define PWMCTL_ENABLE 0x10
#define PWMCTL_SRC_OSC 0x1
#define CM_PWMDIV_REG (0xa4 / 4)
#define PWMDIV_DIVI(divi) (divi << 12) // bits 23-12, max 4095
#define PWM_CLOCK_HZ 19200000.0
#define PWM_RESOLUTION 0.000005 // 5 usec resolution
#define PWM_MSEC_TO_COUNT(ms) ((ms) / PWM_RESOLUTION / 1000.0)
#define PULSE_WIDTH_RESOLUTION .00001 // .01 msec resolution
#define SERVO_MODE_MOVE_ONE 0
#define SERVO_MODE_MOVE_STEPS 1
#define SERVO_MODE_MOVE_LIMIT 2
#define SERVO_IDLE 0
#define SERVO_NEW_MOVE 1
#define SERVO_MOVING 2
#define SERVO_STOP 3
typedef struct
{
pthread_mutex_t mutex;
int status;
int pan,
tilt,
delay;
}
ServoControl;
static ServoControl servo_control;
/* Pointers to mapped peripheral registers.
*/
static volatile uint32_t *gpio_mmap;
static volatile uint32_t *pwm_mmap;
static volatile uint32_t *clock_mmap;
static pthread_t servo_thread_ref;
static void (*pwm_width_func)(int channel, int width, boolean invert);
static FILE *fblaster;
static float pan_cur = 150.0,
tilt_cur = 150.0;
static int pan_channel,
tilt_channel;
/* Servo pulse width units are .01 msec (so width = 150 is 1.5 msec)
*/
void
pwm_width_hardware(int channel, int width, boolean invert)
{
uint32_t count;
int reg;
if (channel == 1)
reg = PWM_DAT1_REG;
else if (channel == 2)
reg = PWM_DAT2_REG;
else
return;
if (invert)
width = 300 - width; // 150 msec is center
if (width < SERVO_MIN_WIDTH)
width = SERVO_MIN_WIDTH;
if (width > SERVO_MAX_WIDTH)
width = SERVO_MAX_WIDTH;
count = (uint32_t) (PULSE_WIDTH_RESOLUTION / PWM_RESOLUTION) * width;
*(pwm_mmap + reg) = count;
}
void
pwm_width_servoblaster(int channel, int width, boolean invert)
{
char buf[64];
static boolean logged = FALSE;
if (channel < 0)
return;
if (!fblaster)
fblaster = fopen("/dev/servoblaster", "w");
if (!fblaster)
{
if (!logged)
log_printf_no_timestamp("Failed to open /dev/servoblaster: %m\n");
logged = TRUE;
return;
}
if (invert)
width = 300 - width;
if (width < SERVO_MIN_WIDTH)
width = SERVO_MIN_WIDTH;
if (width > SERVO_MAX_WIDTH)
width = SERVO_MAX_WIDTH;
snprintf(buf, sizeof(buf), "%d=%d\n", channel, width);
fwrite(buf, strlen(buf), 1, fblaster);
fflush(fblaster);
}
void
gpio_to_channel(int gpio, int *channel, int *altfn)
{
int chan = -1, alt = -1;
if (gpio == 12 || gpio == 18)
{
chan = 1;
alt = (gpio == 18) ? 5 : 0;
}
if (gpio == 13 || gpio == 19)
{
chan = 2;
alt = (gpio == 19) ? 5 : 0;
}
if (channel)
*channel = chan;
if (altfn)
*altfn = alt;
}
void
servo_get_position(int *pan, int *tilt)
{
if (pan)
*pan = (int) pan_cur;
if (tilt)
*tilt = (int) tilt_cur;
}
/* GPFSELn registers start at offset zero from gpio_mmap.
| BCM2835-ARM-Peripherals.pdf pg 91, 10 gpios per GPFSELn with mode bits:
*/
static unsigned int gpfsel_mode_table[] =
{
/* in out alt0 alt1 alt2 alt3 alt4 alt5 */
0b000, 0b001, 0b100, 0b101, 0b110, 0b111, 0b011, 0b010
};
void
gpio_alt_function(int pin, int altfn)
{
int reg = pin / 10,
shift = (pin % 10) * 3;
if (altfn >= 0 && altfn <= 5)
*(gpio_mmap + reg) = (*(gpio_mmap + reg) & ~(0x7 << shift))
| (gpfsel_mode_table[altfn + 2] << shift);
}
void
gpio_set_mode(int pin, int mode) /* mode 0:input 1:output */
{
int reg = pin / 10,
shift = (pin % 10) * 3;
if (mode == 0 || mode == 1)
*(gpio_mmap + reg) = (*(gpio_mmap + reg) & ~(0x7 << shift))
| (gpfsel_mode_table[mode] << shift);
}
/* BCM2835-ARM-Peripherals.pdf pg 101 - GPIO Pull-up/down sequence
*/
void
gpio_set_pud(int pin, int pud)
{
int reg = GPPUDCLK_REG + ((pin > 31) ? 1 : 0);
if (pud != PUD_DOWN && pud != PUD_UP)
return;
*(gpio_mmap + GPPUD_REG) = pud;
usleep(2); // min wait of 150 cycles
*(gpio_mmap + reg) = 1 << (pin & 0x1f);
usleep(2);
*(gpio_mmap + GPPUD_REG) = 0;
*(gpio_mmap + reg) = 0;
}
int
gpio_read(int pin)
{
int reg = GPLEV_REG + ((pin > 31) ? 1 : 0);
return (*(gpio_mmap + reg) & (1 << (pin & 0x1f)) ? 1 : 0 );
}
void
gpio_write(int pin, int level)
{
int reg = ((level == 0) ? GPCLR_REG : GPSET_REG) + ((pin > 31) ? 1 : 0);
*(gpio_mmap + reg) = 1 << (pin & 0x1f);
}
int
pi_model(void)
{
FILE *f;
static int model;
char buf[200], arm[32];
if (model == 0)
{
if ((f = fopen("/proc/cpuinfo", "r")) != NULL)
{
while (fgets(buf, sizeof(buf), f) != NULL)
{
if (sscanf(buf, "model name %*s %31s", arm) > 0)
{
if (!strcmp(arm, "ARMv7"))
model = 2;
else
model = 1;
break;
}
}
fclose(f);
}
}
return model;
}
static void
_servo_move(int pan, int tilt, int delay)
{
float pan_inc, tilt_inc;
int pan_delta, tilt_delta, max_delta, i;
if (pan_channel < 0 && tilt_channel < 0)
return;
servo_control.status = SERVO_MOVING;
pikrellcam.servo_moving = TRUE;
if (pan_cur == 0)
pan_cur = (float) pan;
if (tilt_cur == 0)
tilt_cur = (float) tilt;
pan_delta = pan - pan_cur;
tilt_delta = tilt - tilt_cur;
max_delta = MAX(abs(pan_delta), abs(tilt_delta));
pan_inc = (abs(pan_delta) > 1.0)
? (float) pan_delta / (float) max_delta : 0;
tilt_inc = (abs(tilt_delta) > 1.0)
? (float) tilt_delta / (float) max_delta : 0;
//printf("pan: %d pan_cur:%.0f pan_delta:%d pan_inc:%.2f\n",
// pan, pan_cur, pan_delta, pan_inc);
//printf("tilt:%d tilt_cur:%.0f tilt_delta:%d tilt_inc:%.2f\n",
// tilt, tilt_cur, tilt_delta, tilt_inc);
for (i = 1; i < max_delta && delay > 0; ++i)
{
pan_cur += pan_inc;
tilt_cur += tilt_inc;
pwm_width_func(pan_channel, (int) pan_cur, pikrellcam.servo_pan_invert);
pwm_width_func(tilt_channel, (int) tilt_cur, pikrellcam.servo_tilt_invert);
usleep(delay * 1000);
pthread_mutex_lock(&servo_control.mutex);
if (servo_control.status != SERVO_MOVING)
{
pan_cur = floorf(pan_cur);
tilt_cur = floorf(tilt_cur);
if (servo_control.status != SERVO_NEW_MOVE)
{
preset_on_check((int) pan_cur, (int) tilt_cur);
pthread_mutex_unlock(&servo_control.mutex);
usleep(pikrellcam.servo_settle_msec * 1000);
pikrellcam.servo_moving = FALSE;
pikrellcam.state_modified = TRUE;
}
else
pthread_mutex_unlock(&servo_control.mutex);
return;
}
pthread_mutex_unlock(&servo_control.mutex);
}
pan_cur = (float) pan;
tilt_cur = (float) tilt;
pwm_width_func(pan_channel, pan, pikrellcam.servo_pan_invert);
pwm_width_func(tilt_channel, tilt, pikrellcam.servo_tilt_invert);
pthread_mutex_lock(&servo_control.mutex);
if (servo_control.status != SERVO_NEW_MOVE)
{
servo_control.status = SERVO_IDLE;
preset_on_check(pan, tilt);
pthread_mutex_unlock(&servo_control.mutex);
usleep(pikrellcam.servo_settle_msec * 1000);
pikrellcam.servo_moving = FALSE;
pikrellcam.state_modified = TRUE;
}
else
pthread_mutex_unlock(&servo_control.mutex);
}
static void *
servo_thread(void *ptr)
{
static boolean first_move_done;
while (1)
{
if (servo_control.status != SERVO_NEW_MOVE)
{
usleep(50000);
continue;
}
if (!first_move_done) /* Minimize possible current spike */
{
pikrellcam.servo_moving = TRUE;
pthread_mutex_lock(&servo_control.mutex);
pwm_width_func(pan_channel, servo_control.pan, pikrellcam.servo_pan_invert);
usleep(300000);
pwm_width_func(tilt_channel, servo_control.tilt, pikrellcam.servo_tilt_invert);
usleep(300000);
pan_cur = servo_control.pan;
tilt_cur = servo_control.tilt;
servo_control.status = SERVO_IDLE;
preset_on_check((int) pan_cur, (int) tilt_cur);
pthread_mutex_unlock(&servo_control.mutex);
pikrellcam.servo_moving = FALSE;
pikrellcam.state_modified = TRUE;
}
else
_servo_move(servo_control.pan, servo_control.tilt, servo_control.delay);
first_move_done = TRUE;
}
return NULL;
}
void
servo_move(int pan, int tilt, int delay)
{
if (!pikrellcam.have_servos)
return;
pthread_mutex_lock(&servo_control.mutex);
servo_control.pan = pan;
servo_control.tilt = tilt;
servo_control.delay = delay;
servo_control.status = SERVO_NEW_MOVE;
pikrellcam.on_preset = FALSE;
pthread_mutex_unlock(&servo_control.mutex);
}
void
servo_init(void)
{
int fd, peripheral_base, pan_alt, tilt_alt;
uint32_t divi, t1, t2;
pan_cur = tilt_cur = 150.0;
if (!pikrellcam.have_servos)
return;
if (pikrellcam.servo_use_servoblaster)
{
pwm_width_func = pwm_width_servoblaster;
pan_channel = pikrellcam.servo_pan_gpio;
tilt_channel = pikrellcam.servo_tilt_gpio;
pthread_create (&servo_thread_ref, NULL, servo_thread, NULL);
log_printf_no_timestamp("======= Servo using ServoBlaster (%d %d)\n",
pan_channel, tilt_channel);
return;
}
pwm_width_func = pwm_width_hardware;
gpio_to_channel(pikrellcam.servo_pan_gpio, &pan_channel, &pan_alt);
gpio_to_channel(pikrellcam.servo_tilt_gpio, &tilt_channel, &tilt_alt);
if ( (pan_channel < 0 && tilt_channel < 0)
|| (pan_channel == tilt_channel)
)
{
log_printf_no_timestamp("======= Servo init failed, bad gpio numbers: %s\n",
(pan_channel == tilt_channel && pan_channel != -1) ?
"PWM channel collision" : "gpio number not a hardware PWM");
pan_channel = tilt_channel = -1;
return;
}
peripheral_base = (pi_model() == 2) ? PI_2_PERIPHERAL_BASE : PI_1_PERIPHERAL_BASE;
if ((fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0)
{
pan_channel = tilt_channel = -1;
log_printf_no_timestamp("======= Servo init failed: /dev/mem open failed.");
return;
}
gpio_mmap = (uint32_t *) mmap(NULL, 0x100, PROT_READ|PROT_WRITE, MAP_SHARED,
fd, peripheral_base + GPIO_BASE);
pwm_mmap = (uint32_t *) mmap(NULL, 0x100, PROT_READ|PROT_WRITE, MAP_SHARED,
fd, peripheral_base + PWM_BASE);
clock_mmap = (uint32_t *) mmap(NULL, 0x100, PROT_READ|PROT_WRITE, MAP_SHARED,
fd, peripheral_base + CLOCK_BASE);
close(fd);
if (pwm_mmap == MAP_FAILED || clock_mmap == MAP_FAILED)
{
pan_channel = tilt_channel = -1;
log_printf_no_timestamp("======= Servo init failed: PWM gpio mmap() failed");
return;
}
if (pan_channel > 0)
gpio_alt_function(pikrellcam.servo_pan_gpio, pan_alt);
if (tilt_channel > 0)
gpio_alt_function(pikrellcam.servo_tilt_gpio, tilt_alt);
*(clock_mmap + CM_PWMCTL_REG) = CM_PASSWORD | PWMCTL_KILL;
usleep(10);
divi = (uint32_t) (PWM_CLOCK_HZ * PWM_RESOLUTION);
*(clock_mmap + CM_PWMDIV_REG) = CM_PASSWORD | PWMDIV_DIVI(divi);
*(clock_mmap + CM_PWMCTL_REG) = CM_PASSWORD | PWMCTL_ENABLE | PWMCTL_SRC_OSC;
/* Turn off PWM, set range registers and enable clocks so PWM channels run
| in M/S mode where data count gives pulse width range count is period.
*/
*(pwm_mmap + PWM_CTL_REG) = CTL_REG_RESET_STATE;
usleep(50);
*(pwm_mmap + PWM_RNG1_REG) = (uint32_t) PWM_MSEC_TO_COUNT(20);
*(pwm_mmap + PWM_RNG2_REG) = (uint32_t) PWM_MSEC_TO_COUNT(20);
*(pwm_mmap + PWM_CTL_REG) = CTL_REG_PWM1_MS_MODE | CTL_REG_PWM2_MS_MODE;
/* t1 & t2 is pulse width time in .01 msec units.
*/
t1 = *(pwm_mmap + PWM_DAT1_REG);
t1 = (uint32_t) ((float) t1 * PWM_RESOLUTION / PULSE_WIDTH_RESOLUTION);
t2 = *(pwm_mmap + PWM_DAT2_REG);
t2 = (uint32_t) ((float) t2 * PWM_RESOLUTION / PULSE_WIDTH_RESOLUTION);
pan_cur = (float) ((pan_channel == 1) ? t1 : t2);
if (pikrellcam.servo_pan_invert)
pan_cur = 300.0 - pan_cur;
if (pan_cur < pikrellcam.servo_pan_min || pan_cur > pikrellcam.servo_pan_max)
pan_cur = 150;
tilt_cur = (float) ((tilt_channel == 1) ? t1 : t2);
if (pikrellcam.servo_tilt_invert)
tilt_cur = 300.0 - tilt_cur;
if (tilt_cur < pikrellcam.servo_tilt_min || tilt_cur > pikrellcam.servo_tilt_max)
tilt_cur = 150;
log_printf_no_timestamp("======= Servo using hardware PWM (%d %d)\n",
pikrellcam.servo_pan_gpio, pikrellcam.servo_tilt_gpio);
pthread_create (&servo_thread_ref, NULL, servo_thread, NULL);
}
#define PAN_LEFT 0
#define PAN_RIGHT 1
#define TILT_UP 2
#define TILT_DOWN 3
typedef struct
{
char *name;
int id,
n_args;
}
ServoCommand;
static ServoCommand servo_commands[] =
{
{ "pan_left", PAN_LEFT, 1 },
{ "pan_right", PAN_RIGHT, 1 },
{ "tilt_up", TILT_UP, 1 },
{ "tilt_down", TILT_DOWN, 1 },
};
#define N_SERVO_COMMANDS (sizeof(servo_commands) / sizeof(ServoCommand))
static int
servo_move_position(int cur, int dir, int mode, int limit)
{
if (mode == SERVO_MODE_MOVE_ONE)
cur += dir;
else if (mode == SERVO_MODE_MOVE_STEPS)
cur += dir * pikrellcam.servo_move_steps;
else if (mode == SERVO_MODE_MOVE_LIMIT)
cur = limit;
return cur;
}
void
servo_command(char *cmd_line)
{
ServoCommand *scmd;
int i, n, id = -1;
int pan, tilt, mode;
char buf[64], arg1[32];
static int prev_id;
if (!pikrellcam.have_servos)
return;
arg1[0] = '\0';
n = sscanf(cmd_line, "%63s %31s", buf, arg1);
if (n < 1)
return;
for (i = 0; i < N_SERVO_COMMANDS; ++i)
{
scmd = &servo_commands[i];
if (!strcmp(scmd->name, buf))
{
if (scmd->n_args <= n - 1)
id = scmd->id;
break;
}
}
if (id == -1)
{
// inform_message("Bad motion command.");
return;
}
pan = (int) pan_cur;
tilt = (int) tilt_cur;
mode = atoi(arg1);
if ( mode == SERVO_MODE_MOVE_LIMIT
&& servo_control.status == SERVO_MOVING
&& id == prev_id
)
{
servo_control.status = SERVO_IDLE; /* Force to idle */
return;
}
prev_id = id;
switch (id)
{
case PAN_LEFT:
pan = servo_move_position(pan, -1, mode, pikrellcam.servo_pan_min);
if (pan < pikrellcam.servo_pan_min)
pan = pikrellcam.servo_pan_min;
servo_move(pan, tilt, pikrellcam.servo_move_step_msec);
break;
case PAN_RIGHT:
pan = servo_move_position(pan, 1, mode, pikrellcam.servo_pan_max);
if (pan > pikrellcam.servo_pan_max)
pan = pikrellcam.servo_pan_max;
servo_move(pan, tilt, pikrellcam.servo_move_step_msec);
break;
case TILT_UP:
tilt = servo_move_position(tilt, 1, mode, pikrellcam.servo_tilt_max);
if (tilt > pikrellcam.servo_tilt_max)
tilt = pikrellcam.servo_tilt_max;
servo_move(pan, tilt, pikrellcam.servo_move_step_msec);
break;
case TILT_DOWN:
tilt = servo_move_position(tilt, -1, mode, pikrellcam.servo_tilt_min);
if (tilt < pikrellcam.servo_tilt_min)
tilt = pikrellcam.servo_tilt_min;
servo_move(pan, tilt, pikrellcam.servo_move_step_msec);
break;
}
}

View File

@ -84,7 +84,7 @@ void setup_h264_tcp_server(void)
if(bind (listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr))<0);
{
perror("Server: error binding\n");
// perror("Server: error binding\n");
log_printf("Server: error binding\n");
//return;
}
@ -95,7 +95,8 @@ void setup_h264_tcp_server(void)
save_fd |= O_NONBLOCK;
fcntl( listenfd, F_SETFL, save_fd );
fprintf(stderr,"%s\n","Server running...waiting for connections.");
// fprintf(stderr,"%s\n","Server running...waiting for connections.");
log_printf("Server running...waiting for connections.\n");
}
@ -116,9 +117,9 @@ void tcp_poll_connect(void)
{
h264_conn_status=H264_TCP_SEND_HEADER; //must send header
num_sent=0;
fprintf (stderr, "Server: connect from host %s, port %u.\n",
inet_ntoa (cliaddr.sin_addr),
ntohs (cliaddr.sin_port));
// fprintf (stderr, "Server: connect from host %s, port %u.\n",
// inet_ntoa (cliaddr.sin_addr),
// ntohs (cliaddr.sin_port));
log_printf("Server: connect from host %s, port %u.\n",
inet_ntoa (cliaddr.sin_addr),
ntohs (cliaddr.sin_port));

View File

@ -45,6 +45,8 @@ static struct buffer buffers[NUM_CIRC_BUFS];
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int new_connection_log_count;
/* returned a new buffer, after use the buffer must be free'd with image_buffer_free() */
static struct buffer* client_queue_get()
{
@ -87,9 +89,10 @@ static void* handle_client(void *args)
char header[MAX_BUF_SIZE];
struct buffer *buf = NULL;
log_printf("new connection from host '%s' on port '%d'\n",
inet_ntoa(client->sockaddr.sin_addr),
ntohs(client->sockaddr.sin_port));
if (++new_connection_log_count < 30) /* punt - FIXME */
log_printf("new connection from host '%s' on port '%d'\n",
inet_ntoa(client->sockaddr.sin_addr),
ntohs(client->sockaddr.sin_port));
/* We had delayed the circular buffer allocation until atleast one client is
* connected to the socket. let allocate the buffer now */
@ -127,14 +130,16 @@ static void* handle_client(void *args)
image_buffer_free(buf);
}
failed:
log_printf("closing connection from host '%s' on port '%d'\n",
inet_ntoa(client->sockaddr.sin_addr),
ntohs(client->sockaddr.sin_port));
if (new_connection_log_count < 30) /* punt - FIXME */
log_printf("closing connection from host '%s' on port '%d'\n",
inet_ntoa(client->sockaddr.sin_addr),
ntohs(client->sockaddr.sin_port));
image_buffer_free(buf);
if (data)
free(data);
close(client->fd);
free(client);
pthread_detach(pthread_self());
return NULL;

View File

@ -1,7 +1,7 @@
<?php
// Do not edit this file. Edit config-user.php instead.
//
$config_event_count = 17;
$config_event_count = 18;
$n_columns = 4;
$name_style = "short";
@ -23,6 +23,7 @@ $background_image = "images/paper1.png";
$archive_initial_view = "thumbs";
$archive_thumbs_scrolled = "yes";
$media_thumbs_scrolled = "yes";
$videos_mode = "thumbs";
$video_url = "";
$include_control = "no";
@ -33,7 +34,7 @@ function config_user_save()
global $default_text_color, $selected_text_color, $media_text_color, $manual_video_text_color;
global $n_log_scroll_pixels, $log_text_color, $n_thumb_scroll_pixels, $background_image;
global $config_event_count, $include_control;
global $archive_initial_view, $archive_thumbs_scrolled, $media_thumbs_scrolled;
global $archive_initial_view, $archive_thumbs_scrolled, $media_thumbs_scrolled, $videos_mode;
global $video_url;
$file = fopen("config-user.php", "w");
@ -89,6 +90,7 @@ function config_user_save()
."//\n");
fwrite($file, "define(\"NAME_STYLE\", \"$name_style\");\n");
fwrite($file, "define(\"N_COLUMNS\", \"$n_columns\");\n");
fwrite($file, "define(\"VIDEOS_MODE\", \"$videos_mode\");\n");
fwrite($file, "define(\"ARCHIVE_INITIAL_VIEW\", \"$archive_initial_view\");\n");
fwrite($file, "define(\"ARCHIVE_THUMBS_SCROLLED\", \"$archive_thumbs_scrolled\");\n");
fwrite($file, "define(\"MEDIA_THUMBS_SCROLLED\", \"$media_thumbs_scrolled\");\n\n");
@ -127,6 +129,8 @@ if (defined('MEDIA_TEXT_COLOR'))
if (defined('MANUAL_VIDEO_TEXT_COLOR'))
$manual_video_text_color = MANUAL_VIDEO_TEXT_COLOR;
if (defined('VIDEOS_MODE'))
$videos_mode = VIDEOS_MODE;
if (defined('ARCHIVE_INITIAL_VIEW'))
$archive_initial_view = ARCHIVE_INITIAL_VIEW;
if (defined('ARCHIVE_THUMBS_SCROLLED'))

View File

@ -15,14 +15,15 @@
define("ARCHIVE_DIR", "archive");
// The mjpeg file can be changed by editing ~/.pikrellcam/pikrellcam.conf
// The others are fixed by the install and enforced by the startup script.
// It is no use to change these here.
// These are set up by the install or pikrellcam.conf and enforced by
// the startup script. It is no use to change these here.
//
define("LOG_FILE", "/tmp/pikrellcam.log");
define("MJPEG_FILE", "/run/pikrellcam/mjpeg.jpg");
define("PIKRELLCAM", "/home/pi/pikrellcam/pikrellcam");
define("FIFO_FILE", "/home/pi/pikrellcam/www/FIFO");
define("VERSION", "2.1.12");
define("SERVOS_ENABLE", "servos_off");
define("VERSION", "3.0.0");
?>

View File

@ -64,6 +64,41 @@ And there is a Raspberry Pi
Under construction...
</div>
<span style='font-size: 1.5em; font-weight: 650;'>Version 3.0 Upgrade Notice for Version 2.x Users</span><hr>
<div class='indent0'>
The upgrade from PiKrellcam V2.x to PiKrellCam V3.0 adds presets and servo control.<br>
This is documented below on this page, but there are changes in how
motion regions usage is handled that is important to be aware of up front:
<ul>
<li> The use of saving and loading of motion regions by name is no longer the primary
way to change the motion regions in effect. Saving and loading regions by name now
has a new role of maintaining a set of motion regions as temporaries for backup or
using as an initial condition to be loaded when creating a new preset.
</li>
<li> If motion regions are edited or a new set loaded by name, the changes are
automatically stored into the current preset (unless you have servos and are off
a preset - see below). So if you edit motion regions the current preset is
changed and there is no need to save by name unless you want the backup.
</li>
<li> When pikrellcam is restarted, it loads the preset you were on when pikrellcam
stopped. If for example, you have a preset 1 set up for default use and a preset 2
for windy conditions, then if you want to be sure that at program start preset 1 is
selected, you should have in at-commands.conf:
<pre>
daily start "@preset goto 1 1"
</pre>
If you have servos, you might want to have a position number other than "1".<br>
This would replace the use of an at command to load regions and if you have such
a startup motion load_regions command, you probably want to take that out. If you
don't take it out and don't have a startup preset goto, the regions will be loaded
into whatever preset you restart with which can be not what you want.
If you do use a startup
preset goto command, also having a startup motion load_regions is likely redundant
because the preset remembers its motion regions.
</li>
</ul>
</div>
<span style='font-size: 1.5em; font-weight: 650;'>Install</span><hr>
<div class='indent0'>
@ -105,7 +140,8 @@ Go to the PiKrellCam web page in your browser (omit the port number if it was le
<span style='font-weight:700'>System</span> panel and start the PiKrellCam prgram
by clicking the button
<span class='btn-control'>Start</span>
<br>After two or three seconds, the preview image from the camera should appear.
<br>After two or three seconds (startup can be slower on a Pi 1),
the preview image from the camera should appear.
If it does not you should get in its place an error image indicating that the
camera could not be started. This can happen if the camera is busy (another program
is using it) or if there is a problem with the ribbon cable camera connection.
@ -119,17 +155,17 @@ Go to the PiKrellCam web page in your browser (omit the port number if it was le
and PiKrellCam is now operating with its default settings.
</li>
<li>Wait for motion to be detected and watch the OSD for the video record progress.<br>
After the video ends, view it by going to the Thumbs (or Videos) page by clicking:
<span style='font-weight:700'>Media:</span> <span class='btn-control'>Thumbs</span>
After the video ends, view it by going to the Videos page by clicking:
<span style='font-weight:700'>Media:</span> <span class='btn-control'>Videos</span>
</li>
<li>On the button bar, click the buttons
<span style='font-weight:700'>Show:</span>
<span class='btn-control'>Preset</span>
<span class='btn-control'>Timelapse</span>
<span class='btn-control'>Regions</span>
<span class='btn-control'>Vectors</span>
<br>to toggle showing information PiKrellCam can display on the OSD.
This information shows real time motion detection information and gives a feel
for motion magnitudes and counts which can be configured to tune motion detection.
When you show Preset, you see the currently configured motion detection vector
and burst values and the motion detect regions in effect. See below.
</li>
<li>A basic first configuration to consider is enabling motion detection to be turned on
each time PiKrellCam is started. To do this, use the OSD menu system:<br>
@ -138,7 +174,7 @@ Go to the PiKrellCam web page in your browser (omit the port number if it was le
Expand the <span style='font-weight:700'>Setup</span> panel.
</li>
<li>In the
<span style='font-weight:700'>Motion</span> group,
<span style='font-weight:700'>Config</span> group,
click the button <span class='btn-control'>Settings</span>
</li>
<li>The OSD will show a horizontal menu with
@ -287,7 +323,7 @@ motion:
</div>
<p>
You can see the results of PiKrellCam's vector processing on the OSD by turning on
the showing of Regions and Vectors. Watching this display will allow you to tune
the showing of Preset and Vectors. Watching this display will allow you to tune
your configured vector limit values to your camera environment. To
get a better look at the vectors, you can temporarily raise the mjpeg_divider
value so the OSD will update more slowly.
@ -311,7 +347,10 @@ value so the OSD will update more slowly.
<li>Sparkles are camera motion vectors that have no neighbors and PiKrellCam
considers them noise and excludes them from the composite vectors.
</li>
<li>Interpreting the two vector count status lines:
<li>The vector count status line will be shown if you set
Setup->Config->Settings->Vector_Counts on and Show: Preset.
<br>
Interpreting the vector count status line:
<ul>
<li><span style='font-weight:700'>any:47 (17.1)</span> This shows there was
a frame total of 47 vectors excluding sparkles passing the magnitude limit test.
@ -352,20 +391,145 @@ value so the OSD will update more slowly.
</ul>
</div>
<span style='font-size: 1.5em; font-weight: 650;'>Servos</span><hr>
<div class='indent0'>
PiKrellCam has built in servo control for cameras mounted on servos.
<ul>
<li><span style='font-weight:700'>Hardware PWM</span>: If servos are connected to
the hardware PWM GPIO pins, PiKrellCam can directly control the PWM signals and the
only configuration needed is to set in pikrellcam.conf
<span style='font-weight:700'>servo_pan_gpio</span> and
<span style='font-weight:700'>servo_tilt_gpio</span>
to the PWM GPIO pin numbers. So the pan/tilt or tilt/pan gpio pairs must be one of<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span style='font-weight:700'>12,13 &nbsp; 12,19 &nbsp; 18,13 &nbsp; 18,19</span><br>
Stop pikrellcam before editing ~/.pikrellcam/pikrellcam.conf to set the GPIO values.
Then restart pikrellcam, reload the web page and you will have new buttons to control
position presets and moving the servos.
</li>
<li><span style='font-weight:700'>ServoBlaster</span>: If your servos are connected to
GPIOs that are not the hardware PWM pins you can use ServoBlaster.
Set
<span style='font-weight:700'>servo_pan_gpio</span> and
<span style='font-weight:700'>servo_tilt_gpio</span>
in pikrellcam.conf to the ServoBlaster servo numbers you are using and set
<span style='font-weight:700'>servo_use_servoblaster</span> to
<span style='font-weight:700'>on</span>.<br>
For this a separate install of ServoBlaster is required according to ServoBlaster
documentation.<br>
Stop pikrellcam before editing ~/.pikrellcam/pikrellcam.conf to set the use
servoblaster option and gpio values to ServoBlaster servo numbers.
Then restart pikrellcam and reload the web page.
</li>
</ul>
After configuring for servos, the first thing to do is to check if the servos move
in the right direction when the servo arrow buttons are clicked. If they do not,
then the directions can be inverted in
<nobr><span style='font-weight:700'>Setup->Config->Servo</span></nobr>
</div>
<span style='font-size: 1.5em; font-weight: 650;'>Presets</span><hr>
<div class='indent0'>
<img src="images/preset-servos.jpg" alt="preset-servos.jpg">
<p>
A preset is a camera position with a group of motion detect settings
(vector magnitude / count and burst count / frames) and a set of motion regions.
Clicking the preset up/down
arrows moves to a new settings preset which single click loads a completely new set of
motion detect settings and motion regions. So presets can be configured with
motion detect sensitivities and motion regions appropriate for different weather
or other conditions and quickly selected with single clicks.
<p>
Preset left/right arrow buttons are shown only if servos are configured and
move the servos to configured position presets.
<p>
The Servo button and arrows are shown only if servos are enabled. Click the
<span class='btn-control'>Servo</span>
button to cycle the servo arrow direction buttons through three modes: step by one, step by
<span style='font-weight:700'>Move_Steps</span>, and scan. When arrow buttons are in scan
mode, clicking an arrow will step the servo continuously at
<span style='font-weight:700'>Move_Step_msec</span>
rate until the arrow button is clicked
again or the servo reaches a pan/tilt limit.
<p>
<span style='font-weight:700'>Preset behavior without servos:</span>
<ul>
<li> PiKrellCam considers the camera at a single fixed position and will never be
off a preset. There will be only preset up/down arrows and
no preset left/right or servo arrows. The pan/tilt graphics in the above image
will not be shown.
</li>
<li> Any motion settings or regions edits will immediately apply to the currently
selected settings preset (Preset up/down arrows).
</li>
<li> To create a new settings preset, click
<nobr><span style='font-weight:700'>Setup->Preset->New</span></nobr>
and a new settings preset will be created with the existing
motion settings and regions which can then be edited.
</li>
</ul>
<span style='font-weight:700'>Preset behavior with servos:</span>
<ul>
<li> If <nobr><span style='font-weight:700'>Setup->Config->Servo->Motion_Off_Preset</span></nobr> is
<span style='font-weight:700'>OFF</span>,
motion detection applies only if the servos are on a preset and if the servos are
moved off a position preset with the servo arrow buttons
then motion detection is put on hold. Set this option to
<span style='font-weight:700'>ON</span>
if you want to have motion detected even if a servo position is off a preset.
</li>
<li> Presets cannot be created with different tilt positions at the same pan
position.
</li>
<li> When the servos are on a position preset, a new settings for the position
is created with
<nobr><span style='font-weight:700'>Setup->Preset->New</span></nobr>
</li>
<li> To create a preset at a new position:
<ul>
<li>Move the servos to the desired position and click <br>
&nbsp;&nbsp;&nbsp;<span style='font-weight:700'>Setup->Preset->New</span><br>
and you can then edit the settings and motion regions for the preset.
<br>
Or you may move the servos off a preset and edit
the settings or regions before creating the new preset and the OSD will warn you
that you will need to create a new preset or else your edits will not be saved.
If you move the servos back to an existing preset before creating a new one,
your edits will be replaced with the preset settings.
</li>
<li>Copy an existing set of settings from a position preset to a new preset at
a new position.<br>
To do this, first move the servos to the existing preset you want to
copy the settings from.
Then, use the Servo arrows to move the camera to the new position (don't let
the servo position fall on any other preset), and click<br>
&nbsp;&nbsp;&nbsp;<span style='font-weight:700'>Setup->Preset->Copy</span><br>
</li>
</ul>
</li>
</ul>
</div>
<span style='font-size: 1.5em; font-weight: 650;'>Motion Regions Panel</span><hr>
<img src="images/motion-regions.jpg" alt="motion-regions.jpg">
<div class='indent0'>
As motion regions are edited, they are saved to the current preset unless servos are
configured and the servo position is not on a preset. If the servo position is not on
a preset, motion region edits will be lost unless you create a new preset or save the
motion regions by name.
<p>
Motion regions outline areas of the camera view that will be
sensitive to motion and provides for excluding from motion detection areas such
as wind blown vegetation.
Motion regions may be added, deleted, resized or moved. After the
motion regions have been edited, they may be saved and loaded by name. Typically, a region
will be configured for a particular camera setup and then saved by name. Then that motion region
setup can be automatically loaded when PiKrellCam starts - see the example in the at commands
section. If you save an edited motion regions to the special name
<span style='font-weight:700'>default</span>, then your modified regions will load
at startup without having to configure an at command motion regions load.
Motion regions may be added, deleted, resized or moved at each preset.
Motion regions may also be saved by name and this provides a way to maintain a set of
motion regions as a backup or a temporary. For example, a backup motion region by
name can be loaded as an initial condition after creating a new preset. Or temporary
motion regions by name can be loaded if you have a set of different motion regions you
want to load to a preset on demand for evaluation.
If a motion region is loaded by name it is automatically saved to the
current preset unless you have servos and are off a preset.
<p>
The increment of a region resize or move can be coarse or fine by selecting/deselecting the
<span style='font-weight:700'>Coarse Move</span> select button. When the increment is fine,
@ -403,51 +567,16 @@ After a menu is opened and an option or value from it is highlighted
by clicking the arrow buttons, the option or value must be finalized by clicking the
<span class='btn-control' >Sel</span>
button. This is required for the change to be saved in the configuration file.
<p>
<span style='font-size: 1.2em; font-weight: 680;'>Camera Config</span>
If servos are not configured, there will be no Move or Copy buttons in the
Preset group and there will be no Servo button in the Config group.
<p>
<span style='font-size: 1.2em; font-weight: 650;'>Preset</span>
<ul>
<li><span style='font-weight:700'>Video Presets</span> - selects the video resolution for
motion and manual videos. Different resolutions may have different fields of view. So
one reason for selecting
<span style='font-weight:700'>720p</span> over
<span style='font-weight:700'>1080p</span> would be to get a wider field of view. Resolutions
will have either 16:9 or 4:3 aspect ratio.
</li>
<li><span style='font-weight:700'>Still Presets</span> - selecting different resolutions
gives different fields of view and aspect ratios.
</li>
<li><span style='font-weight:700'>Adjustments</span>
<ul>
<li><span style='font-weight:700'>video_bitrate</span> - determines the size of a video
and its quality. Adjust up if it improves video quality. Adjust down if you want
to reduce the size of the videos.
</li>
<li><span style='font-weight:700'>video_fps</span> - typically this should be no higher
than 24 or the motion detecting preview jpeg camera stream may start dropping frames.
(I have no data on the effect GPU overclocking might have on this limitation).
</li>
<li><span style='font-weight:700'>video_mp4box_fps</span> - keep this value the same
as video_fps unless you want to create fast or slow motion videos.
</li>
<li><span style='font-weight:700'>mjpeg_divider</span> - this value is divided into
the video_fps value to get the preview jpeg rate. The preview is updated at this rate
and it is the rate that motion vector frames are checked for motion.
</li>
<li><span style='font-weight:700'>still_quality</span> - adjust up if it improves
still jpeg quality. Adjust down if you want to reduce the size of still jpegs.
</li>
</ul>
</li>
</ul>
<span style='font-size: 1.2em; font-weight: 650;'>Camera Params</span>
<div class='indent1'>
The camera parameters menus can set the hardware parameters of the Pi camera.
</div>
<p>
<span style='font-size: 1.2em; font-weight: 650;'>Motion</span>
<ul>
<li><span style='font-weight:700'>Vector Limits</span>
<li><span style='font-weight:700'>Settings</span><br>
These values are part of a preset and editing them applies to the currently
selected preset. If you have servos and are off a preset, editing these values
can be done in anticipation of creating a new preset.
<ul>
<li><span style='font-weight:700'>Vector_Magnitude</span> - sets the minimum magnitude
of a motion vector. Individual motion vector and region composite vector
@ -489,6 +618,121 @@ button. This is required for the change to be saved in the configuration file.
</li>
</ul>
</li>
<li><span style='font-weight:700'>Move: One</span><br>
You will have this button only if servos are configured.<br>
If the servos are moved off a preset, click this if you want to move
the preset you were on to the current servo position.
</li>
<li><span style='font-weight:700'>Move: All</span><br>
You will have this button only if servos are configured.<br>
If the servos are moved off a preset, click this if you want to move
the preset you were on to the current servo position and move all the other preset
positions by the same amount. If the camera installation is disturbed or serviced,
this allows a quick adjustment for restoring position presets. The other presets
may still need small adjustments if servo positioning is non linear. All presets
cannot be moved if the move would move any preset past a servo position limit.
</li>
<li><span style='font-weight:700'>Del</span><br>
If servos are not configured or if the servo position is on an existing preset, delete
the current Settings. If servos are configured and the servo position is on a preset
and the Settings are the last Settings for the preset, then delete the position preset
unless it is the only existing position preset. There must always be at least one
preset and you cannot delete down to zero presets.
</li>
<li><span style='font-weight:700'>Copy</span><br>
You will have this button only if servos are configured.<br>
If the pan servo is moved off a preset, click this to create a
new preset at the servo position which is initiallized by copying all of
the preset settings (motion detect limits and regions) from the preset you
were on into the new preset.
</li>
<li><span style='font-weight:700'>New</span><br>
Creates a new preset. If servos are not configured or if the servo position is on an
existing position preset, this will create a new Settings preset which can then
be edited. If servos are configured and the servo position is not on an existing preset,
then a new position preset is created with an initial single Settings.
</li>
</ul>
<p>
<span style='font-size: 1.2em; font-weight: 650;'>Time Lapse</span>
<div class='indent1'>
Enter a time period in the text box and click <span style='font-weight:700'>Start</span>
to begin a time lapse run. Entering a new period and clicking
<span style='font-weight:700'>Start</span> will change the period for the current time
lapse run and does not start a new time lapse run. Click the
<span style='font-weight:700'>Timelapse</span> button on the button bar to show the time
lapse status on the preview OSD. When the
<span style='font-weight:700'>End</span> button is clicked PiKrellCam will run a time lapse
end script which will convert the time lapse images into a video and store the final video
in the media <span style='font-weight:700'>videos</span> directory and the video name
will have a
<span style='font-weight:700'>tl_</span> prefix.
The progress of this
conversion will be shown on the time lapse OSD display. To better control start, end and
overnight hold times, a time lapse can be controlled with at commands. See that section for
an example.
</div>
<p>
<span style='font-size: 1.2em; font-weight: 680;'>Config</span>
<ul>
<li><span style='font-weight:700'>Video Res</span> - selects the video resolution for
motion and manual videos. Different resolutions may have different fields of view. So
one reason for selecting
<span style='font-weight:700'>720p</span> over
<span style='font-weight:700'>1080p</span> would be to get a wider field of view. Resolutions
will have either 16:9 or 4:3 aspect ratio.
</li>
<li><span style='font-weight:700'>Still Res</span> - selecting different resolutions
gives different fields of view and aspect ratios.
</li>
<li><span style='font-weight:700'>Settings</span>
<ul>
<li><span style='font-weight:700'>Startup_Motion</span> - set to
<span style='font-weight:700'>ON</span> for motion detection to be enabled each time
PiKrellCam starts. If set to
<span style='font-weight:700'>OFF</span>, motion detection will need to be manually
enabled from the web page or a script.
</li>
<li><span style='font-weight:700'>video_bitrate</span> - determines the size of a video
and its quality. Adjust up if it improves video quality. Adjust down if you want
to reduce the size of the videos.
</li>
<li><span style='font-weight:700'>video_fps</span> - typically this should be no higher
than 24 or the motion detecting preview jpeg camera stream may start dropping frames.
(I have no data on the effect GPU overclocking might have on this limitation).
</li>
<li><span style='font-weight:700'>video_mp4box_fps</span> - keep this value set to zero
unless you want to create fast or slow motion videos. When zero, mp4 boxing fps will be
the same as video_fps which is normally what you want. But this value can be set to a
non zero value different from video_fps if you want fast or slow motion videos.
</li>
<li><span style='font-weight:700'>mjpeg_divider</span> - this value is divided into
the video_fps value to get the preview jpeg rate. The preview is updated at this rate
and it is the rate that motion vector frames are checked for motion.
</li>
<li><span style='font-weight:700'>still_quality</span> - adjust up if it improves
still jpeg quality. Adjust down if you want to reduce the size of still jpegs.
</li>
<li><span style='font-weight:700'>Vector_Counts</span> - enable showing of vector count
statistics when showing a Preset. This information may help when setting motion detect
limits.
</li>
<li><span style='font-weight:700'>Vector_Dimming</span> - sets a percentage dimming
of the preview jpeg image when the
<span style='font-weight:700'>Vectors</span> display is enabled. This is to improve
the contrast of the drawn motion vectors.
</li>
<li><span style='font-weight:700'>Preview_Clean</span> - if set to
<span style='font-weight:700'>OFF</span>, whatever text or graphics that happen to be
drawn on the preview jpeg at the time a motion preview save or thumb save occurs will
also appear on the saved preview or thumb. This might help with some debugging, but
is normally not desirable, so the option should be set
<span style='font-weight:700'>ON</span>.
</li>
</ul>
</li>
<li><span style='font-weight:700'>Times</span>
<ul>
<li><span style='font-weight:700'>Confirm_Gap</span> - for motion direction detects,
@ -539,46 +783,49 @@ button. This is required for the change to be saved in the configuration file.
is configured.
</div>
</li>
<li><span style='font-weight:700'>Settings</span>
<li><span style='font-weight:700'>Servo</span><br>
The Servo menu is shown only if servos have been configured.
<ul>
<li><span style='font-weight:700'>Startup_Motion</span> - set to
<span style='font-weight:700'>ON</span> for motion detection to be enabled each time
PiKrellCam starts. If set to
<span style='font-weight:700'>OFF</span>, motion detection will need to be manually
enabled from the web page or a script.
<li><span style='font-weight:700'>Motion_Off_Preset</span> - if
<span style='font-weight:700'>OFF</span>, do not detect motion when the servo postion
is off a preset. If configured motion regions are suitable for any servo position,
this can be set
<span style='font-weight:700'>ON</span> if you do not want motion detection to be
put on hold when a servo is manually moved off a preset.
</li>
<li><span style='font-weight:700'>Vector_Dimming</span> - sets a percentage dimming
of the preview jpeg image when the
<span style='font-weight:700'>Vectors</span> display is enabled. This is to improve
the contrast of the drawn motion vectors.
<li><span style='font-weight:700'>Move_Step_msec</span> - delay in milliseconds between
servo steps when a servo is moved using the
<span style='font-weight:700'>Servo</span> arrow buttons. A servo step changes the
pulse width of the servo control line by 1/100 of a millisecond (10 usec).
</li>
<li><span style='font-weight:700'>Preview_Clean</span> - if set to
<span style='font-weight:700'>OFF</span>, whatever text or graphics that happen to be
drawn on the preview jpeg at the time a motion preview save or thumb save occurs will
also appear on the saved preview or thumb. This might help with some debugging, but
is normally not desirable, so the option should be set
<span style='font-weight:700'>ON</span>.
<li><span style='font-weight:700'>Preset_Step_msec</span> - delay
in milliseconds between servo steps when a servo is moved using the
<span style='font-weight:700'>Preset</span>
left/right arrow buttons.
</li>
<li><span style='font-weight:700'>Servo_Settle_msec</span> - delay in milliseconds before
motion detection is taken out of its hold state after servos stop moving.
</li>
<li><span style='font-weight:700'>Move_Steps</span> - number of steps to move when the
<span style='font-weight:700'>Servo</span> arrow buttons are cycled to the second step
mode. The other step modes are single step and scan and are selected by clicking the
<span style='font-weight:700'>Servo</span> button. When in scan mode, the scan can be
stopped by clicking the double arrow button again.
</li>
<li><span style='font-weight:700'>Pan_Left_Limit</span><br>
<span style='font-weight:700'>Pan_Right_Limit</span><br>
<span style='font-weight:700'>Tilt_Up_Limit</span><br>
<span style='font-weight:700'>Tilt_Down_Limit</span> - Sets the servo limits.
</li>
<li><span style='font-weight:700'>Servo_Pan_Invert</span><br>
<span style='font-weight:700'>Servo_Tilt_Invert</span> - Set these to ON or OFF
to change the direction servos move when the direction arrows are clicked.
</li>
</ul>
</li>
</ul>
<span style='font-size: 1.2em; font-weight: 650;'>Time Lapse</span>
<span style='font-size: 1.2em; font-weight: 650;'>Camera Params</span>
<div class='indent1'>
Enter a time period in the text box and click <span style='font-weight:700'>Start</span>
to begin a time lapse run. Entering a new period and clicking
<span style='font-weight:700'>Start</span> will change the period for the current time
lapse run and does not start a new time lapse run. Click the
<span style='font-weight:700'>Timelapse</span> button on the button bar to show the time
lapse status on the preview OSD. When the
<span style='font-weight:700'>End</span> button is clicked PiKrellCam will run a time lapse
end script which will convert the time lapse images into a video and store the final video
in the media <span style='font-weight:700'>videos</span> directory and the video name
will have a
<span style='font-weight:700'>tl_</span> prefix.
The progress of this
conversion will be shown on the time lapse OSD display. To better control start, end and
overnight hold times, a time lapse can be controlled with at commands. See that section for
an example.
The camera parameters menus can set the hardware parameters of the Pi camera.
</div>
</div>
@ -864,6 +1111,12 @@ motion show_vectors [on|off|toggle]
motion [command] - other commands sent by the web page to edit motion regions not
intented for script or command line use.
preset prev_position
preset next_position
preset prev_settings
preset next_settings
preset goto position settings
display [command] - commands sent by the web page to display OSD menus. Not intended for
script or command line use.
@ -1126,8 +1379,7 @@ recognized.
<li>
At each PiKrellCam startup
<ol>
<li>Load a motion detect regions file named
<span style='font-weight:700'>driveway</span>.
<li>Goto a preset.
</li>
<li> Prepend the hostname to the annotated text date string drawn on each video
<br>
@ -1136,7 +1388,10 @@ At each PiKrellCam startup
</li>
</ol>
<pre>
daily start "@motion load_regions driveway"
# If no servos, goto position 1 (only 1 position possible with no servos) settings 1:
daily start "@preset goto 1 1"
# If servos, goto position 3 settings 1
daily start "@preset goto 3 1"
daily start "@annotate_string prepend start1 $H_"
</pre>
</li>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
www/images/arrow0-down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
www/images/arrow0-left.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
www/images/arrow0-right.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
www/images/arrow0-up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
www/images/arrow2-down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
www/images/arrow2-up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,15 +1,64 @@
<script>
var servo_mode = 0;
var servo_left_array =
[
"images/arrow0-left.png",
"images/arrow-left.png",
"images/arrow2-left.png"
];
var servo_right_array =
[
"images/arrow0-right.png",
"images/arrow-right.png",
"images/arrow2-right.png"
];
var servo_up_array =
[
"images/arrow0-up.png",
"images/arrow-up.png",
"images/arrow2-up.png"
];
var servo_down_array =
[
"images/arrow0-down.png",
"images/arrow-down.png",
"images/arrow2-down.png"
];
function servo_move_mode()
{
servo_mode += 1;
if (servo_mode > servo_left_array.length - 1)
servo_mode = 0;
document.getElementById("servo_left").src = servo_left_array[servo_mode];
document.getElementById("servo_right").src = servo_right_array[servo_mode];
document.getElementById("servo_up").src = servo_up_array[servo_mode];
document.getElementById("servo_down").src = servo_down_array[servo_mode];
}
function servo_move_command(pan_tilt)
{
// alert("motion " + move_mode + " " + where);
fifo_command("servo " + pan_tilt + " " + servo_mode);
}
</script>
<?php
//ini_set('display_errors',1);
//ini_set('display_startup_errors',1);
//error_reporting(-1);
require_once(dirname(__FILE__) . '/config.php');
if (file_exists("config-user.php"))
require_once(dirname(__FILE__) . '/config.php');
include_once(dirname(__FILE__) . '/config-user.php');
include_once(dirname(__FILE__) . '/config-defaults.php');
include_once(dirname(__FILE__) . '/config-defaults.php');
function time_lapse_period()
{
@ -50,7 +99,7 @@ echo "<body background=\"$background_image\" onload=\"mjpeg_start();\">";
echo "<div class=\"text-center\" style=\"color: $default_text_color; font-size: 1.4em;\">";
echo "<img id=\"mjpeg_image\"
alt=\"No preview jpeg. Is pikrellcam running? Click: System->Start\"
style=\"border:6px groove silver;\"
style=\"border:4px groove silver;\"
onclick=\"image_expand_toggle();\"
></div>";
?>
@ -74,10 +123,59 @@ echo "<body background=\"$background_image\" onload=\"mjpeg_start();\">";
<input type="image" src="images/shutter.png"
width="30" height="30"
onclick="fifo_command('still')"
style="margin-left:20px; vertical-align: bottom;"
style="margin-left:16px; vertical-align: bottom;"
>
<?php
if (defined('SERVOS_ENABLE'))
$servos_enable = SERVOS_ENABLE;
else
$servos_enable = "servos_off";
echo "<span style=\"margin-left:20px; color: $default_text_color\">Preset:</span>";
echo "<input type='image' id='preset_up' src='images/arrow-up.png'
style='margin-left:2px; vertical-align: bottom;'
onclick=\"fifo_command('preset next_settings')\">";
echo "<input type='image' id='preset_down' src='images/arrow-down.png'
style='margin-left:2px; vertical-align: bottom;'
onclick=\"fifo_command('preset prev_settings')\">";
if ($servos_enable == "servos_on")
{
echo "<input type='image' id='preset_left' src='images/arrow-left.png'
style='margin-left:2px; vertical-align: bottom;'
onclick=\"fifo_command('preset prev_position')\">";
echo "<input type='image' id='preset_right' src='images/arrow-right.png'
style='margin-left:2px; vertical-align: bottom;'
onclick=\"fifo_command('preset next_position')\">";
}
if ($servos_enable == "servos_on")
{
// echo "<span style=\"margin-left:20px; color: $default_text_color\">Servo:</span>";
// background: rgba(255, 255, 255, 0.16);
echo "<input id='servo_move_mode' type='button' value=\"Servo:\"
class=\"btn-control\"
style=\"cursor: pointer;
background: rgba(0, 0, 0, 0.08);
color: $default_text_color; margin-left:20px; padding-left:2px; padding-right:0px;\"
onclick='servo_move_mode();'>";
echo "<input type='image' id='servo_left' src='images/arrow0-left.png'
style='margin-left:2px; vertical-align: bottom;'
onclick=\"servo_move_command('pan_left')\">";
echo "<input type='image' id='servo_right' src='images/arrow0-right.png'
style='margin-left:2px; vertical-align: bottom;'
onclick=\"servo_move_command('pan_right')\">";
echo "<input type='image' id='servo_up' src='images/arrow0-up.png'
style='margin-left:2px; vertical-align: bottom;'
onclick=\"servo_move_command('tilt_up')\">";
echo "<input type='image' id='servo_down' src='images/arrow0-down.png'
style='margin-left:2px; vertical-align: bottom;'
onclick=\"servo_move_command('tilt_down')\">";
}
if (defined('INCLUDE_CONTROL'))
{
if ($include_control == "yes")
@ -98,30 +196,30 @@ if (file_exists("custom-control.php"))
class="btn-control"
style="margin-right:20px;"
>Archive Calendar</a>
<?php echo "<span style=\"color: $default_text_color\"> Media:</span>"; ?>
<a href="media-archive.php?mode=media&type=videos"
class="btn-control"
>Videos</a>
<a href="media-archive.php?mode=media&type=thumbs"
class="btn-control"
>Thumbs</a>
<a href="media-archive.php?mode=media&type=stills"
class="btn-control"
style="margin-right:30px;"
>Stills</a>
<?php
echo "<span style=\"color: $default_text_color\"> Media:</span>";
echo "<a href='media-archive.php?mode=media&type=videos'
style='margin-left:2px;'
class='btn-control'
>Videos</a>";
echo "<a href='media-archive.php?mode=media&type=stills'
class='btn-control'
style='margin-left:2px; margin-right:30px;'
>Stills</a>";
echo "<span style=\"color: $default_text_color\"> Enable:</span>";
?>
<?php echo "<span style=\"color: $default_text_color\"> Enable:</span>"; ?>
<input type="button" id="motion_button" value="Motion"
onclick="fifo_command('motion_enable toggle')"
class="btn-control motion-control"
>
<?php echo "<span style=\"float: right; color: $default_text_color\"> Show:"; ?>
<input id="timelapse_button" type="button" value="Timelapse"
onclick="fifo_command('tl_show_status toggle')"
<input type="button" id="regions_button" value="Preset"
onclick="fifo_command('motion show_regions toggle')"
class="btn-control motion-control"
>
<input type="button" id="regions_button" value="Regions"
onclick="fifo_command('motion show_regions toggle')"
<input id="timelapse_button" type="button" value="Timelapse"
onclick="fifo_command('tl_show_status toggle')"
class="btn-control motion-control"
>
<input type="button" id="vectors_button" value="Vectors"
@ -132,7 +230,197 @@ if (file_exists("custom-control.php"))
</div>
<div id="container">
<div class="expandable-panel" id="cp-1">
<div class="expandable-panel-heading">
<h3>Setup<span class="icon-close-open"></span></h3>
</div>
<div class="expandable-panel-content">
<table class="table-container">
<tr>
<td style="border: 0;" align="right">
<input type="image" src="images/arrow2-left.png"
style="padding:0px 0px 0px 0px; margin:0;"
onclick="fifo_command('display <<');"
>
<input type="image" src="images/arrow-left.png"
style="padding:0px 0px 0px 0px; margin:0;"
onclick="fifo_command('display <');"
>
</td>
<td style="border: 0;" align="center">
<input type="button" value="SEL"
class="btn-control"
onclick="fifo_command('display sel');"
>
</td>
<td style="border: 0;" align="left">
<input type="image" src="images/arrow-right.png"
style="padding:0px 0px 0px 0px; margin:0;"
onclick="fifo_command('display >');"
>
<input type="image" src="images/arrow2-right.png"
style="padding:0px 0px 0px 0px; margin:0;"
onclick="fifo_command('display >>');"
>
</td>
</tr>
<tr>
<td style="border: 0;" align="right" >
</td>
<td style="border: 0;" align="center">
<input type="button" value="Back"
onclick="fifo_command('display back');"
class="btn-control"
>
</td>
<td style="border: 0;" align="left" >
</td>
</tr>
</table>
<table class="table-container">
<tr>
<td>
<?php echo "<span style=\"font-weight:600; color: $default_text_color\">Preset</span>"; ?>
<div>
<input type="button" value="Settings"
class="btn-menu"
style="margin-left:40px"
onclick="fifo_command('display motion_limit');"
>
<?php
if ($servos_enable == "servos_on")
{
echo "<span style=\"margin-left:20px; margin-right:0px; color: $default_text_color\">Move:";
echo "<input type='button' value='One'
class='btn-menu'
style='margin-left:2px; margin-right:0px;'
onclick=\"fifo_command('preset move_one')\">";
echo "<input type='button' value='All'
class='btn-menu'
style='margin-left:4px;'
onclick=\"fifo_command('preset move_all')\">";
}
?>
<input type="button" value="New"
class="btn-menu"
style="float: right; margin-left:6px"
onclick="fifo_command('preset new');"
>
<?php
if ($servos_enable == "servos_on")
{
echo "<input type='button' value='Copy'
class='btn-menu'
style='float: right; margin-left:6px'
onclick=\"fifo_command('preset copy')\">";
}
?>
<input type="button" value="Del"
class="btn-menu alert-control"
style="float: right;margin-left:20px"
onclick="fifo_command('preset delete');"
>
</div>
</td>
</tr>
<tr>
<td>
<?php echo "<span style=\"font-weight:600; color: $default_text_color\"> Time Lapse </span>"; ?>
<div>
<?php echo "<span style=\"margin-left:40px; font-weight:600; color: $default_text_color\"> Period </span>"; ?>
<input type="text" id="tl_period" value="<?php echo time_lapse_period(); ?>" size="3"
>
<?php echo "<span style=\"margin-left:4px; color: $default_text_color\"> sec </span>"; ?>
<input type="button" value="Start"
class="btn-menu"
onclick="tl_start();"
style="float: right; margin-left:10px;"
>
<input type="button" value="Hold"
class="btn-menu"
onclick="fifo_command('tl_hold toggle');"
style="float: right; margin-left:10px;"
>
<input type="button" value="End"
class="btn-menu alert-control"
onclick="fifo_command('tl_end');"
style="float: right;"
>
</div>
</td>
</tr>
<tr>
<td>
<?php echo "<span style=\"font-weight:600; color: $default_text_color\">Config</span>"; ?>
<div>
<input type="button" value="Video Res"
class="btn-menu"
style="margin-left:40px"
onclick="fifo_command('display video_presets');"
>
<input type="button" value="Still Res"
class="btn-menu"
onclick="fifo_command('display still_presets');"
>
<input type="button" value="Settings"
class="btn-menu"
onclick="fifo_command('display settings');"
>
<input type="button" value="Times"
class="btn-menu"
onclick="fifo_command('display motion_time');"
>
<?php
if ($servos_enable == "servos_on")
{
echo "<input type='button' value='Servo'
class='btn-menu'
onclick=\"fifo_command('display servo_settings')\">";
}
?>
</div>
</td>
</tr>
<tr>
<td>
<?php echo "<span style=\"font-weight:600; color: $default_text_color\"> Camera Params </span>"; ?>
<div>
<input type="button" value="Picture"
class="btn-menu"
style= "margin-left:40px"
onclick="fifo_command('display picture');"
>
<input type="button" value="Meter"
class="btn-menu"
onclick="fifo_command('display metering');"
>
<input type="button" value="Exposure"
class="btn-menu"
onclick="fifo_command('display exposure');"
>
<input type="button" value="White Bal"
class="btn-menu"
onclick="fifo_command('display white_balance');"
>
<input type="button" value="Image Effect"
class="btn-menu"
onclick="fifo_command('display image_effect');"
>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="expandable-panel" id="cp-2">
<div class="expandable-panel-heading">
<h3>Motion Regions<span class="icon-close-open"></span></h3>
</div>
@ -144,7 +432,7 @@ if (file_exists("custom-control.php"))
<table cellpadding="0" cellspacing="0" border="0" table-layout="fixed">
<tr>
<td style="border: 0;" >
<input type="button" value="List" style="margin-right: 12px;"
<input type="button" value="List" style="margin-right: 20px;"
onclick="list_regions();"
class="btn-control"
>
@ -154,7 +442,7 @@ if (file_exists("custom-control.php"))
class="btn-menu"
>
</td>
<td style="border: 0;" align="right">
<td style="border: 0;" align="left">
<input type="text" id="save_regions" size=6 >
<input type="button" value="Save"
onclick="save_regions();"
@ -164,10 +452,10 @@ if (file_exists("custom-control.php"))
</tr>
<tr>
<td style="border: 0;" align="left">
</td>
<td style="border: 0;" align="left">
</td>
<td style="border: 0;" align="right">
<?php echo "<span style=\"color: $default_text_color\">
<?php echo "<span style=\"color: $default_text_color; margin-left: 12px;\">
Coarse Move</span>"; ?>
<input type="checkbox" name="move_mode"
onclick='move_region_mode(this);' checked>
@ -175,25 +463,25 @@ if (file_exists("custom-control.php"))
</tr>
<tr align="right">
<td style="border: 0;" align="right">
<td style="border: 0;" align="left">
<input type="button" value="New"
onclick="new_region();"
class="btn-control"
>
<input type="button" value="Delete" style="margin-right: 8px;"
<input type="button" value="Del" style="margin-left: 8px;"
onclick="fifo_command('motion delete_regions selected');"
class="btn-control alert-control"
>
</td>
<td style="border: 0;" align="right">
<?php echo "<span style=\"color: $default_text_color\">Select</span>"; ?>
<input type="button" value="<"
<?php echo "<span style=\"color: $default_text_color;\">Select</span>"; ?>
<input type='image' src='images/arrow0-left.png'
style="vertical-align: bottom;"
onclick="fifo_command('motion select_region <');"
class="btn-control"
>
<input type="button" value=">"
<input type='image' src='images/arrow0-right.png'
style="vertical-align: bottom;"
onclick="fifo_command('motion select_region >');"
class="btn-control"
>
</td>
</tr>
@ -275,153 +563,8 @@ if (file_exists("custom-control.php"))
</table>
</div>
</div>
<div class="expandable-panel" id="cp-2">
<div class="expandable-panel-heading">
<h3>Setup<span class="icon-close-open"></span></h3>
</div>
<div class="expandable-panel-content">
<table class="table-container">
<tr>
<td style="border: 0;" align="right">
<input type="image" src="images/arrow2-left.png"
style="padding:0px 0px 0px 0px; margin:0;"
onclick="fifo_command('display <<');"
>
<input type="image" src="images/arrow-left.png"
style="padding:0px 0px 0px 0px; margin:0;"
onclick="fifo_command('display <');"
>
</td>
<td style="border: 0;" align="center">
<input type="button" value="SEL"
class="btn-control"
onclick="fifo_command('display sel');"
>
</td>
<td style="border: 0;" align="left">
<input type="image" src="images/arrow-right.png"
style="padding:0px 0px 0px 0px; margin:0;"
onclick="fifo_command('display >');"
class="btn-control"
>
<input type="image" src="images/arrow2-right.png"
style="padding:0px 0px 0px 0px; margin:0;"
onclick="fifo_command('display >>');"
>
</td>
</tr>
<tr>
<td style="border: 0;" align="right" >
</td>
<td style="border: 0;" align="center">
<input type="button" value="Back"
onclick="fifo_command('display back');"
class="btn-control"
>
</td>
<td style="border: 0;" align="left" >
</td>
</tr>
</table>
<table class="table-container">
<tr>
<td>
<?php echo "<span style=\"font-weight:600; color: $default_text_color\"> Camera Config </span>"; ?>
<div class="text-center">
<input type="button" value="Video Presets"
class="btn-menu"
onclick="fifo_command('display video_presets');"
>
<input type="button" value="Still Presets"
class="btn-menu"
onclick="fifo_command('display still_presets');"
>
<input type="button" value="Adjustments"
class="btn-menu"
onclick="fifo_command('display camera_adjustments');"
>
</div>
</td>
</tr>
<tr>
<td>
<?php echo "<span style=\"font-weight:600; color: $default_text_color\"> Camera Params </span>"; ?>
<div class="text-center">
<input type="button" value="Picture"
class="btn-menu"
onclick="fifo_command('display picture');"
>
<input type="button" value="Metering"
class="btn-menu"
onclick="fifo_command('display metering');"
>
<input type="button" value="Exposure"
class="btn-menu"
onclick="fifo_command('display exposure');"
>
<input type="button" value="White Balance"
class="btn-menu"
onclick="fifo_command('display white_balance');"
>
<input type="button" value="Image Effect"
class="btn-menu"
onclick="fifo_command('display image_effect');"
>
</div>
</td>
</tr>
<tr>
<td>
<?php echo "<span style=\"font-weight:600; color: $default_text_color\"> Motion </span>"; ?>
<div class="text-center" >
<input type="button" value="Vector Limits"
class="btn-menu"
onclick="fifo_command('display motion_limit');"
>
<input type="button" value="Times"
class="btn-menu"
onclick="fifo_command('display motion_time');"
>
<input type="button" value="Settings"
class="btn-menu"
onclick="fifo_command('display motion_setting');"
>
</div>
</td>
</tr>
<tr>
<td>
<?php echo "<span style=\"font-weight:600; color: $default_text_color\"> Time Lapse </span>"; ?>
<div>
<?php echo "<span style=\"margin-left:40px; font-weight:600; color: $default_text_color\"> Period </span>"; ?>
<input type="text" id="tl_period" value="<?php echo time_lapse_period(); ?>" size="3"
>
<?php echo "<span style=\"margin-left:4px; color: $default_text_color\"> sec </span>"; ?>
<input type="button" value="Start"
class="btn-menu"
onclick="tl_start();"
style="float: right; margin-left:10px;"
>
<input type="button" value="Hold"
class="btn-menu"
onclick="fifo_command('tl_hold toggle');"
style="float: right; margin-left:10px;"
>
<input type="button" value="End"
class="btn-menu alert-control"
onclick="fifo_command('tl_end');"
style="float: right;"
>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="expandable-panel" id="cp-3">
<div class="expandable-panel-heading">

View File

@ -5,7 +5,8 @@ h3, p, ol, ul, li {
margin:0px;
padding:0px;
font-size:0.85em;
font-family:Arial, Helvetica, sans-serif;
// font-family:Arial, Helvetica, sans-serif;
font-family:Serif, Helvetica, sans-serif;
}
ol, ul {
padding:3px 0 10px 22px;

View File

@ -77,9 +77,10 @@ selected
-webkit-border-radius: 5;
-moz-border-radius: 5;
border-radius: 5px;
font-family: Arial;
// font-family: Arial;
font-family: Serif;
color: #000000;
font-size: 1.0em;
font-size: 0.9em;
// background: #c1c6d1;
background: #c7c7c7;
padding: 3px 6px 3px 6px;
@ -99,9 +100,10 @@ selected
-webkit-border-radius: 5;
-moz-border-radius: 5;
border-radius: 7px;
font-family: Arial;
// font-family: Arial;
font-family: Serif;
color: #000000;
font-size: 1.0em;
font-size: 0.9em;
// background: #9daaad;
background: #bac1c2;
padding: 3px 6px 3px 6px;

View File

@ -32,7 +32,7 @@ function image_expand_toggle()
function new_region()
{
fifo_command("motion add_region 0.3 0.3 0.3 0.3");
fifo_command("motion new_region 0.3 0.3 0.3 0.3");
// alert("Two consecutive fifo_command() not working.");
// fifo_command("motion select_region last\n");
}

View File

@ -60,10 +60,10 @@ function eng_filesize($bytes, $decimals = 1)
function media_dir_array_create($media_dir)
{
global $archive_root, $media_mode, $media_type;
global $archive_root, $media_mode, $media_type, $media_subdir;
$media_array = array();
$file_dir = "$media_dir/$media_type"; // videos, thumbs, or stills
$file_dir = "$media_dir/$media_subdir"; // videos, thumbs, or stills
if (is_dir($file_dir))
{
@ -98,9 +98,9 @@ function media_dir_array_create($media_dir)
if ($mtime == 0)
$mtime = filemtime("$file_dir" . "/" . "$file_name");
$ymd = date("Y-m-d", $mtime);
if ("$media_type" == "videos" || "$media_type" == "thumbs")
if ("$media_type" == "videos")
{
if ("$media_type" == "videos")
if ("$media_subdir" == "videos")
{
$thumb_name = str_replace(".mp4", ".th.jpg", $file_name);
$short_name = date('H:i:s', $mtime) . "$extension";
@ -195,12 +195,12 @@ function media_array_index($name)
function delete_file($media_dir, $fname)
{
global $media_mode, $media_type;
global $media_mode, $media_subdir;
if (!is_dir($media_dir))
return;
if ("$media_type" == "stills")
if ("$media_subdir" == "stills")
unlink("$media_dir/stills/$fname");
else
{
@ -229,7 +229,7 @@ function delete_day($media_dir, $ymd)
if (!is_dir($media_dir))
return;
if ("$media_type" == "videos" || "$media_type" == "thumbs")
if ("$media_type" == "videos")
{
array_map('unlink', glob("$media_dir/videos/*$ymd*.mp4"));
array_map('unlink', glob("$media_dir/videos/*$ymd*.csv"));
@ -248,7 +248,7 @@ function delete_all_files($media_dir)
if (!is_dir($media_dir))
return;
if ("$media_type" == "videos" || "$media_type" == "thumbs")
if ("$media_type" == "videos")
{
array_map('unlink', glob("$media_dir/videos/*.mp4"));
array_map('unlink', glob("$media_dir/videos/*.csv"));
@ -329,7 +329,7 @@ function wait_files_gone($key, $pat)
break;
else if ("$key" == "day")
{
if ("$media_type" == "videos" || "$media_type" == "thumbs")
if ("$media_type" == "videos")
{
if ( count(glob("$media_dir/videos/*$pat*")) == 0
&& count(glob("$media_dir/thumbs/*$pat*")) == 0
@ -397,9 +397,11 @@ function restart_page($selected)
$year = "";
if (isset($_GET["newtype"]))
$media_type = $_GET["newtype"]; // "videos", "stills", or "thumbs"
$media_type = $_GET["newtype"]; // "videos" or "stills"
else if (isset($_GET["type"]))
$media_type = $_GET["type"]; // "videos", "stills", or "thumbs"
$media_type = $_GET["type"]; // "videos" or "stills"
if ("$media_type" == "thumbs")
$media_type = "videos"; // transition from old version
if (isset($_GET["label"])) // Descriptive label of media_mode
$label = $_GET["label"];
@ -425,6 +427,27 @@ function restart_page($selected)
$env = "mode=$media_mode&type=$media_type";
}
if (isset($_GET["videos_mode_list"]))
{
$videos_mode = "list";
config_user_save();
}
if (isset($_GET["videos_mode_thumbs"]))
{
$videos_mode = "thumbs";
config_user_save();
}
if ("$media_type" == "stills")
$media_subdir = "stills";
else if ("$videos_mode" == "thumbs")
$media_subdir = "thumbs";
else
$media_subdir = "videos";
//echo "<script type='text/javascript'>alert('$media_dir $media_type $media_subdir $videos_mode');</script>";
//echo "<script type='text/javascript'>alert('$env');</script>";
if (isset($_GET["toggle_scroll"]))
{
if ("$media_mode" == "archive")
@ -500,7 +523,7 @@ function restart_page($selected)
$fname = $_GET["archive"]; // mp4 passed for videos or thumbs type
$ymd = $_GET["date"];
$fifo = fopen(FIFO_FILE,"w");
if ("$media_type" == "videos" || "$media_type" == "thumbs")
if ("$media_type" == "videos")
{
fwrite($fifo, "archive_video $fname $ymd");
$subdir = "videos";
@ -519,7 +542,7 @@ function restart_page($selected)
{
$ymd = $_GET["archive_date"];
$fifo = fopen(FIFO_FILE,"w");
if ("$media_type" == "videos" || "$media_type" == "thumbs")
if ("$media_type" == "videos")
fwrite($fifo, "archive_video day $ymd");
else
fwrite($fifo, "archive_still day $ymd");
@ -554,7 +577,7 @@ function restart_page($selected)
media_array_create();
$index = media_array_index("$selected");
if ( "$media_type" == "thumbs" &&
if ( "$media_subdir" == "thumbs" &&
( ("$media_mode" == "archive" && "$archive_thumbs_scrolled" == "no")
|| ("$media_mode" == "media" && "$media_thumbs_scrolled" == "no")
)
@ -572,20 +595,20 @@ function restart_page($selected)
echo "<a href=$file_path target='_blank'>
<img src=\"$file_path\"
style='max-width:100%;'
style='border:6px groove silver;'>
style='border:4px groove silver;'>
</a>";
else if ("$scrolled" == "yes")
{
$thumb_path = $media_array[$index]['thumb_path'];
echo "<video controls width='640' style='border:6px groove silver;'>
echo "<video controls width='640' style='border:4px groove silver;'>
<source src=\"$file_path\" type='video/mp4'>
Your browser does not support the video tag.
</video>";
if (is_file($thumb_path))
echo "<img src=\"$thumb_path\" style='border:6px groove silver;'>";
echo "<img src=\"$thumb_path\" style='border:4px groove silver;'>";
else
echo "<img src=\"$background_image\"
style='width:150px; height:150px; border:6px groove silver;'>";
style='width:150px; height:150px; border:4px groove silver;'>";
}
if ("$scrolled" == "yes")
{
@ -657,44 +680,35 @@ function restart_page($selected)
echo "<span style=\"font-size: 1.2em; font-weight: 500;\">
$media_label</span>";
$uctype = ucfirst($media_type);
if ("$media_type" == "videos")
{
echo "<span style=\"margin-left: 16px; font-size: 1.2em; font-weight: 500;\">
$uctype</span>";
echo "<a href=\"media-archive.php?newtype=thumbs&$env\"
class='btn-control' style='margin-left:8px;'>Thumbs</a>";
echo "<a href=\"media-archive.php?newtype=stills&$env\"
class='btn-control' style='margin-left:8px;'>Stills</a>";
}
else if ("$media_type" == "thumbs")
{
echo "<a href=\"media-archive.php?newtype=videos&$env\"
class='btn-control' style='margin-left:16px;'>Videos</a>";
echo "<span style=\"margin-left: 4px; font-size: 1.2em; font-weight: 500;\">
$uctype</span>";
echo "<span style=\"margin-left: 4px; font-size: 1.2em; font-weight: 500;\">Videos</span>";
echo "<a href=\"media-archive.php?newtype=stills&$env\"
class='btn-control' style='margin-left:8px;'>Stills</a>";
}
else if ("$media_type" == "stills")
{
echo "<a href=\"media-archive.php?newtype=videos&$env\"
class='btn-control' style='margin-left:16px;'>Videos</a>";
echo "<a href=\"media-archive.php?newtype=thumbs&$env\"
class='btn-control' style='margin-left:8px;'>Thumbs</a>";
echo "<span style=\"margin-left: 4px; font-size: 1.2em; font-weight: 500;\">
$uctype</span>";
class='btn-control' style='margin-left:8px;'>Videos</a>";
echo "<span style=\"margin-left: 4px; font-size: 1.2em; font-weight: 500;\">Stills</span>";
}
$disk_total = disk_total_space($archive_root);
$disk_free = disk_free_space($archive_root);
$used_percent = sprintf('%.1f',(($disk_total - $disk_free) / $disk_total) * 100);
$free_percent = sprintf('%.1f',($disk_free / $disk_total) * 100);
$total = eng_filesize($disk_total);
$free = eng_filesize($disk_free);
$used = eng_filesize($disk_total - $disk_free);
echo "<span style=\"float: top; margin-left:30px; font-size: 0.96em; font-weight:550; color: $default_text_color\">
Disk:&thinsp;${total}B &nbsp Free:&thinsp;${free}B &nbsp Used:&thinsp;${used}B ($used_percent %)</span>";
Disk:&thinsp;${total}B &nbsp Free:&thinsp;${free}B&thinsp;($free_percent %)</span>";
echo "<span style='float:right;'>";
if ("$videos_mode" == "thumbs")
echo "<a href='media-archive.php?$env&videos_mode_list'>List View</a>";
else
echo "<a href='media-archive.php?$env&videos_mode_thumbs'>Thumbs View</a>";
echo "</span>";
echo "</div>";
if ($media_array_size == 0)
@ -705,7 +719,7 @@ function restart_page($selected)
if ("$scrolled" == "yes")
{
if ("$media_type" == "videos" || "$media_type" == "thumbs")
if ("$media_type" == "videos")
$div_style = "overflow-y: scroll; height:${n_video_scroll_pixels}px; overflow-x: auto; border:4px groove silver";
else
$div_style = "overflow-y: scroll; height:${n_still_scroll_pixels}px; overflow-x: auto; border:4px groove silver";
@ -713,7 +727,7 @@ function restart_page($selected)
else
$div_style = "margin: 20px; border: 4px";
if ("$media_type" == "thumbs")
if ("$media_subdir" == "thumbs")
echo "<form method=\"POST\" action=\"media-archive.php?$env\">";
echo "<div style=\"$div_style\">";
if ("$scrolled" == "yes")
@ -734,13 +748,13 @@ function restart_page($selected)
$date_string</span>";
$ymd_header = $ymd;
$dir = $media_array[$k]['media_dir'];
if ($n_columns > 2 && "$media_type" != "thumbs")
if ($n_columns > 2 && "$media_subdir" != "thumbs")
echo "</td><td>";
if ("$next_select" != "")
$next_file = "&file=$next_select";
else
$next_file = "";
if ("$media_type" == "thumbs")
if ("$media_subdir" == "thumbs")
echo "<input style='margin-left: 16px' type='checkbox' name='checkbox_list[]'
onClick=\"select_day(this, '$ymd')\"/>";
else
@ -752,7 +766,7 @@ function restart_page($selected)
style='margin-left: 32px; margin-bottom:4px; margin-top:24px; font-size: 0.82em; text-align: left;'
onclick='if (confirm(\"Archive day $ymd?\"))
{window.location=\"media-archive.php?$env&dir=$dir&archive_date=$ymd$next_file\";}'>";
if ($n_columns > 2 && "$media_type" != "thumbs")
if ($n_columns > 2 && "$media_subdir" != "thumbs")
echo "</td><td>";
}
echo "<input type='button' value='Delete Day'
@ -768,7 +782,7 @@ function restart_page($selected)
$n_rows = ceil(($last - $k) / $n_columns);
}
if ("$media_type" == "thumbs")
if ("$media_subdir" == "thumbs")
{
echo "<tr><td>";
for ($idx = $k; $idx < $last; ++$idx)
@ -881,7 +895,7 @@ function restart_page($selected)
Archive Calendar</a>";
echo "<span style=\"color: $default_text_color;\">";
if ("$media_type" == "thumbs")
if ("$media_subdir" == "thumbs")
{
echo "<span style='margin-left: 50px;'>Selections:</span>";
if ("$media_mode" != "archive")
@ -928,7 +942,7 @@ function restart_page($selected)
echo "</span>";
echo "</div>";
if ("$media_type" == "thumbs")
if ("$media_subdir" == "thumbs")
echo "</form>";
echo "</div></body></html>";