As of v1.10 vdu_controls
supports hardware light/lux metering for
automatic brightness adjustment.
In order to support a variety of light metering hardware, v1.10 can read metered values from tty's, named-pipes (FIFO's) and executable programs/scripts. I currently use a simple light meter built using an Arduino and a GY30/BH1750 sensor. I've also written a couple of example scripts that uses a Logitech Webcam C270 to achieve an approximate metering value.
The Vdu_controls
Light Meter Dialog
includes controls for setting
up a light meter, setting the brightness auto-adjustment interval, and
defining each VDU's mapping of lux values to VDU brightness:
(Note: the Light Meter Dialog
is only available after enabling lux-options
in
the main Settings Dialog
)
This document discusses some hardware options for lux metering.
An Arduino and a GY30/GY302/BH1750 is the very reliable way of obtaining true Lux values. At the time of writing, the Arduino Nano and a GY30/BH1750 can be purchased for about US$10.
The image below shows a Arduino nano, the GY-30 and the required wiring connections:
The components I purchased required the headers to be added by soldering. If you're not comfortable soldering components, the Nano, and possibly the GY-30, may be obtained with the headers pre-fitted, in which case assembly on a solderless breadboard would be extremely easy. Power and tty communication is via the USB connection to the PC.
As well as the GY30, there is also a GY302/BH1750. The sketch-library I use should work with any of these. But should this not to be the case, issue #80 describes how to get the GY302 working with vdu_controls by using a different sketch-library.
A simple Arduino Sketch using Christopher Laws' BH1750 library will allow a GY30/GY302/BH1750 equipped Arduino to produce a feed of Lux values. The sketch code I'm using is as follows:
#include <BH1750.h>
#include <Wire.h>
BH1750 lightMeter;
void setup() {
Serial.begin(9600);
// Initialize the I2C bus (BH1750 library doesn't do this automatically)
Wire.begin();
// On esp8266 you can select SCL and SDA pins using Wire.begin(D4, D3);
// For Wemos / Lolin D1 Mini Pro and the Ambient Light shield use
// Wire.begin(D2, D1);
lightMeter.begin();
}
void loop() {
float lux = lightMeter.readLightLevel();
Serial.println(lux);
delay(1000);
}
This sketch produces a feed of Lux values to the Nano's USB tty output.
On a Linux host, the corresponding input tty device
would typically be /dev/ttyUSB0
, /dev/ttyUSB1
, ...
The permissions on the Linux system may need to be configured to allow the desktop user access to the USB tty device. You may need to research how to set permissions for your choice of distro.
On some systems this would mean adding the desktop user's username to an
appropriate group. The ls
command can be used to see what user and group
owns the device:
% ls -l /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 0 Apr 7 07:32 /dev/ttyUSB0
The above example is from my OpenSUSE system, which assigns
these devices to the dialout
group. In my case, I'd needed to do:
sudo usermod --append --groups dialout michael
This grants access to all dialout
devices (all ttys and modems) - which
may have other security implications. After adding a user to a group,
the user will have to re-login - it's the easiest way to ensure the
whole desktop session hierarchy takes on the change.
Once the sketch is running and the permissions have been set up, the tty
feed can be directly read by vdu_controls
, just configure the correct
device path in the Light Metering Dialog
.
You may find the /dev/ttyUSB
device number may change as a result of
hot-plugging other USB devices. If you encounter this problem, you can add udev
rules to create a fixed-name symlink to your arduino device. For example:
-
Find out some unique attributes of your arduino such as
ID_MODEL_ID
andID_VENDOR_ID
::% udevadm info /dev/ttyUSB1 | egrep 'ID_USB_VENDOR_ID|ID_USB_MODEL_ID' E: ID_USB_MODEL_ID=7523 E: ID_USB_VENDOR_ID=1a86
-
Use an editor to create a udev rule such as
/etc/udev/rules.d/99-local-arduino.rules
% sudo vi /etc/udev/rules.d/99-local-arduino.rules
The text of the rule should match the id's from above, for example: I use the following rule:
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="arduino"
The rule causes the matched
/dev/ttyUSB[0-9]
to be symbolic linked to the fixed name/dev/arduino
. -
Activate the rule and check that it works:
% sudo udevadm control --reload-rules % sudo udevadm trigger % ls -l /dev/ttyUSB1 /dev/arduino lrwxrwxrwx 1 root root 7 Jun 3 17:35 /dev/arduino -> ttyUSB1 crw-rw---- 1 root dialout 188, 1 Jun 3 17:35 /dev/ttyUSB1
In vdu_controls
, set the lux device to new fixed-name /dev/arduino
instead
of /dev/ttyUSB[0-9]
If you don't wish to build an Arduino based solution, you may be able to use a webcam to achieve usable metering values.
I've developed three scripts that can uses a webcam frame grab to calculate a lux-like value:
-
lux-from-webcam.bash: This bash script averages a webcam capture by using ImageMagick to convert it to a 1 pixel image.
Dependencies:ImageMagick-7
(image conversion software), andv4l-utils
(Video 4 Linux camera controls). -
lux-from-webcam.py: This simple python script averages a webcam capture using OpenCV, otherwise it's pretty much the same as the bash script.
Dependencies:cv2
(OpenCV
python real-time computer vision library). -
vlux_meter.py: This script is an alpha level release of a more user-friendly version of the previous two scripts. It's a python Qt application that runs out of the system-tray. Controls are included for previewing the image capture, setting a sample crop area, and mapping brightness 0..255 to lux values 0..100,000. Dependencies:
pyqt
cv2
(OpenCV
python real-time computer vision library).
The scripts are intended for use with webcams that feature manual exposure controls. Sampling from fixed manual exposures allows relative brightness to be measured by calculating the average brightness in captured images. Such an approach will work reliably for any desktop where the webcam's view is fixed and the only major change throughout the day is the light level.
The vlux_meter.py
script can be restricted to sampling from a fixed
crop within each capture, this might help when using a camera that
lacks fixed exposure controls.
The average brightness in the images can be used to develop a mapping
of brightness values to approximate lux values. The mapping need not be
contiguous, accurate, or realistic, they just need to be produce values
sufficient for use with vdu_controls
.
When attempting to use these scripts with a webcam, it's a good idea to find out whether the webcam has any options for manual exposure and how to configure them. Both of the scripts are set to use options available with a Logitech Webcam C270, the scripts, or their associated config file, may need editing to employ similar options on other webcams.
The Video for Linux
command v4l-ctl --list-ctrls-menu
can be
used to find out what options a webcam supports, for example, here
is the output for a Logitech Webcam C270:
% v4l2-ctl -d /dev/video0 --list-ctrls-menus
User Controls
brightness 0x00980900 (int) : min=0 max=255 step=1 default=128 value=128
contrast 0x00980901 (int) : min=0 max=255 step=1 default=32 value=32
saturation 0x00980902 (int) : min=0 max=255 step=1 default=32 value=32
white_balance_automatic 0x0098090c (bool) : default=1 value=1
gain 0x00980913 (int) : min=0 max=255 step=1 default=0 value=0
power_line_frequency 0x00980918 (menu) : min=0 max=2 default=2 value=2 (60 Hz)
0: Disabled
1: 50 Hz
2: 60 Hz
white_balance_temperature 0x0098091a (int) : min=0 max=10000 step=10 default=4000 value=4000 flags=inactive
sharpness 0x0098091b (int) : min=0 max=255 step=1 default=24 value=24
backlight_compensation 0x0098091c (int) : min=0 max=1 step=1 default=1 value=1
Camera Controls
auto_exposure 0x009a0901 (menu) : min=0 max=3 default=3 value=3 (Aperture Priority Mode)
1: Manual Mode
3: Aperture Priority Mode
exposure_time_absolute 0x009a0902 (int) : min=1 max=10000 step=1 default=166 value=166 flags=inactive
exposure_dynamic_framerate 0x009a0903 (bool) : default=0 value=1
The Logitech Webcam C270 supports auto_exposure
manual setting of
to 1 Manual Mode
. The camera also supports setting
the exposure_time_absolute
to something appropriate for
sampling brightness in the local environment (on Linux,
the exposure time is set in units of 1/exposure_time_absolute
seconds, so 64 would mean 1/64 of a second).
The scripts will require customisation for the local ambient lighting conditions and local camera options. In order to use them you'll need to be comfortable editing and configuring hardware and scripts using the command line. The requirements for the two scripts are available on all major Linux distributions.
The scripts are similar in their approach:
- capture a still image,
- compute the average image-brightness for the captured image (0..255),
- consult a table of image-brightness to lux mappings,
- interpolate (log10) between the matched mapping entries,
- output a single lux value (typically 0 to 10000 on a log10 scale).
The first two scripts read the same two config files:
~/.config/lux-from-webcam.config
- basic parameters. This file is optional. Default values:DATA_FILE=~/.config/lux-from-webcam.data CAMERA_DEVICE=/dev/video0 MANUAL_EXPOSURE_OPTION=1 MANUAL_EXPOSURE_TIME=64 IMAGE_LOCATION=/tmp/lux-from-webcam.jpg
~/.config/lux-from-webcam.data
- lux/brightness mappings. This file will be created on the first run. Please edit it according to local conditions. Default values:SUNLIGHT 100000 250 DAYLIGHT 10000 160 OVERCAST 1000 110 SUNRISE_SUNSET 400 50 DARK_OVERCAST 100 20 LIVING_ROOM 50 5 NIGHT 5 0
- In the data above, the name in the 1st column is a comment noting the circumstances that might result in the lux value in the 2nd column. This comment field is ignored by the script.
- The 2nd column is the lux value to output.
- The 3rd column is the camera image-brightness reading that corresponds to the 2nd column lux value.
The data file can have any number of rows of descending values. The above defaults shown are from a mapping for a study with access to a large amount of natural daylight. They're unlikely to be suitable for other situations.
You can use either the bash script or the python script, or even switch from one to the other. The bash script is slightly faster.
In order to use the first two scripts with vdu_controls
, they must be
set to be executable:
chmod u+x lux-from-webcam.bash
chmod u+x lux-from-webcam.py
Once they are set to be executable, they will be able to be selected
as "Runnable" script in the Light Metering Dialog
. Then
metering can be enabled, and vdu_controls
will periodically
run the selected script to obtain a single lux values.
They can also be run in a shell to experiment with creating new mapping values. They both output diagnostics to stderr, for example:
% /usr/share/vdu_controls/sample-scripts/lux-from-webcam.bash
INFO: camera-brightness: 129/255
INFO: log10 interpolating 129 over 110..160 to lux 1000..10000
INFO: brightness=129, value=110, lux=1031.81, name=OVERCAST
1031.81
vlux_meter.py
can mostly be configured from within its own GUI. It reads
and writes these settings to an INI format config file:
~/.config/vlux_meter/vlux_meter.conf
The config file is reasonably self describing, a sample follows:
[camera]
device = /dev/video0
manual_exposure_option = 1
manual_exposure_time = 64
auto_exposure_option = 3
crop = 3.8750,11.1667,14.8750,27.8333
[brightness_to_lux]
Sunlight = 250 100000
Daylight = 160 10000
Overcast = 110 1000
Rise/set = 50 400
Dusk = 20 100
Room = 5 50
Night = 0 5
Rise-Set = 50 400
[global]
system_tray_enabled = yes
fifo_path = ~/.cache/vlux_fifo
display_frequency_millis = 1000
dispatch_frequency_seconds = 60
translations_enabled = no**
Make sure v4l (Video for Linux) and python3-opencv (a computer vision library)
are installed. Then just run vlux_meter.py
as a normal python script,
It may optionally run from the system-tray (either by passing a command line
argument or by editing the config file):
python3 vlux_meter.py --system-tray|--no-system-tray
Lux values are output to the FIFO $HOME/.cache/vlux_fifo
.
Set vdu_controls
lux-metering to read lux from the FIFO $HOME/.cache/vlux_fifo
Config files are written to $HOME/.config/vlux_meter/vlux_meter.conf
If a webcam doesn't support fixed manual exposures, it may still be
possible to sample from an appropriate crop within each capture.
Cropping is supported by vlux_meter.py
and you might also
add cropping to the other simpler scripts. Cropping might be coupled
with be a specifically tailored target within the crop, perhaps
a card or item in the scene that's 18% grey, or a patterned card
of some sort.
You may also write your own heuristics to guess at the available light.
The guess only has to be good enough for use with vdu_controls
, the
values need not be perfect. Such heuristics would likely be very
specific to local circumstances, I've not explored such an approach
to any great degree, but possibilities include:
- The difference between the brightest and darkest pixels may be greatest when the light source is quite strong.
- At different times of the day different parts of a scene may be dark or light, for example, the ceiling light may only be on during the night.
- The amount of measured image-noise may increase in poor lighting.
You can create your own light metering devices and scripts and
use them with vdu_controls
. The Light Metering Dialog
provides
three options for utilizing a light meter:
- A tty device: provide a stream of values separated by carriage-return linefeed.
- A UNIX FIFO: provide a stream of values separated by linefeed (the normal UNIX line terminator).
- An executable: provide one value on one line each time it is run.
A custom light meter need not supply accurate or realistic values. A
custom meter can produce any useful sequence or set or values in the
range 0 to 10000. Within this closed system, a "lux" value can mean
what ever you want it to mean. The Light Metering Dialog
can be
used to map the custom measured values to an appropriate brightness
levels.
See the vdu_controls(1) man page for further infor on Lux Metering.