Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Further improval of controls #42

Merged
merged 47 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
000696e
Control: change is_readable/is_writable to is_writeonly/is_readonly.
otaku42 Mar 22, 2023
b798ed8
Control: update is_... methods/properties to actually return bools
otaku42 Mar 22, 2023
6144213
Control: reimplement method is_writeable, with correct behaviour.
otaku42 Mar 22, 2023
b8d3a15
Controls are also not writeable while grabbed or when disabled.
otaku42 Mar 24, 2023
88cabd5
Fix flake8 complaint
otaku42 Mar 24, 2023
d5e9dde
Controls: add alternative constructor from_device.
otaku42 Mar 24, 2023
f8629d9
Remove stand-alone function config_name; the functionality has been m…
otaku42 Mar 25, 2023
79fd14d
Control: two minor changes to flags gathering in __repr__ to satisfy …
otaku42 Mar 25, 2023
20a8cda
Begin implementation of inheritance-based class model for controls.
otaku42 Mar 28, 2023
dea5cbe
Adjust example output in README.md to the changes in commit 20a8cda
otaku42 Mar 28, 2023
5640d4e
Dissolve BaseSingleControl into BaseControl and BaseNumericControl.
otaku42 Apr 5, 2023
2b57f1c
Add support for checking all currently defined control flags.
otaku42 Apr 5, 2023
2d68a2c
is_writeonly now is called is_flagged_write_only
otaku42 Apr 7, 2023
5580d4b
After default has been moved to BaseControl, it should be part BaseCo…
otaku42 Apr 7, 2023
685acc2
Make default a property; this is required for an upcoming change
otaku42 Apr 7, 2023
99d79b3
Use actual control type in this exception message
otaku42 Apr 7, 2023
ee38673
Introduce some 'hooks' meant to be overwritten by upcoming subclasses
otaku42 Apr 7, 2023
ff66c02
These checks need to be moved to BaseNumericControl, since BaseContro…
otaku42 Apr 7, 2023
395f4ec
Introduce LegacyControls as backward-compatible controls 'factory', w…
otaku42 Apr 7, 2023
877709c
Controls: introduce a ControlType to class mapping, to support instan…
otaku42 Apr 8, 2023
ed23474
Initial implementation of class BooleanControl.
otaku42 Apr 8, 2023
6c9532f
BaseNumericControl: make clipping of written values to minimum/maximu…
otaku42 Apr 8, 2023
bd9dd8b
BaseControl: value to be written should be converted to target type b…
otaku42 Apr 8, 2023
608954e
Initial implementation of classes IntegerControl and Integer64Control.
otaku42 Apr 8, 2023
7e47ec0
iter_read_menu(): use ctrl._info to support all subclasses of BaseCon…
otaku42 Apr 17, 2023
6e0f5f6
Initial implementation of class MenuControl.
otaku42 Apr 17, 2023
bbac364
Add some explanation of and demonstration for the improved controls API.
otaku42 Apr 17, 2023
832a4a2
BaseControl: fix checks for flags being set
otaku42 Apr 20, 2023
d2fc7ff
Controls: add set_clipping(), allows clipping to be changed for all n…
otaku42 Apr 20, 2023
9392f16
MenuControl: menu controls expect written values to be integers
otaku42 Apr 20, 2023
43c33e7
Add example to demonstrate usage of controls
otaku42 Apr 20, 2023
d18bf00
Use new control interface by default now.
otaku42 Apr 20, 2023
430d947
Fix flake8's 'over-indented' complaint.
otaku42 Apr 20, 2023
d6aa811
Update examples in README.md for new control interface.
otaku42 Apr 20, 2023
8e4ca97
Add link to v4l2py-ctl.py example
otaku42 Apr 22, 2023
10eadd4
Merge remote-tracking branch 'upstream/master' into improve_controls
otaku42 Apr 25, 2023
62bef57
Apply some black magic
otaku42 Apr 25, 2023
d485978
Correct the upper_bound values as per https://github.com/tiagocoutinh…
otaku42 Apr 28, 2023
e910562
Initial implementation of U8/U16/U32Control
otaku42 Apr 29, 2023
22c17d9
Initial "implementation" of GenericControl. Use this now instead of L…
otaku42 Apr 29, 2023
654e47d
Reduce BaseControl to the bare minimum of control features, and move
otaku42 May 2, 2023
da53ed6
Initial implementation of ButtonControl. Not yet tested, since I have
otaku42 May 2, 2023
a9d5f3a
Add new compound control types as per kernel v6.3. Closes otaku42/v4l…
otaku42 May 2, 2023
93fc9b3
Merge branch 'tiagocoutinho:master' into improve_controls
otaku42 May 15, 2023
67a6e3b
Make the web example work with Python < 3.10 (again). Closes otaku42/…
otaku42 May 19, 2023
398f9b3
Update installation instructions for extra dependencies: OpenCV package
otaku42 May 19, 2023
a98df12
Fix error reported by @tiagocoutinho at tiagocoutinho/v4l2py#42.
otaku42 May 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions examples/v4l2py-ctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def show_control_status(device: str, legacy_controls: bool) -> None:
for ctrl in cam.controls.with_class(cc):
print("0x%08x:" % ctrl.id, ctrl)
if isinstance(ctrl, MenuControl):
for (key, value) in ctrl.items():
for key, value in ctrl.items():
print(11 * " ", f" +-- {key}: {value}")
elif isinstance(ctrl, LegacyControl):
for item in ctrl.menu.values():
Expand All @@ -53,15 +53,19 @@ def get_controls(device: str, controls: list, legacy_controls: bool) -> None:
print("")


def set_controls(device: str, controls: list, legacy_controls: bool, clipping: bool) -> None:
controls = ((ctrl.strip(), value.strip()) for (ctrl, value) in
(c.split("=") for c in controls))
def set_controls(
device: str, controls: list, legacy_controls: bool, clipping: bool
) -> None:
controls = (
(ctrl.strip(), value.strip())
for (ctrl, value) in (c.split("=") for c in controls)
)

with Device(device, legacy_controls=legacy_controls) as cam:
print("Changing value of given controls ...\n")

cam.controls.set_clipping(clipping)
for (control, value_new) in controls:
for control, value_new in controls:
ctrl = _get_ctrl(cam, control)
if not ctrl:
print(f"{control}: unknown control")
Expand All @@ -85,7 +89,9 @@ def set_controls(device: str, controls: list, legacy_controls: bool, clipping: b
if success:
print(f"{result} {control}: {value_old} -> {value_new}\n")
else:
print(f"{result} {control}: {value_old} -> {value_new}\n{result} {reason}\n")
print(
f"{result} {control}: {value_old} -> {value_new}\n{result} {reason}\n"
)


def reset_controls(device: str, controls: list, legacy_controls: bool) -> None:
Expand Down Expand Up @@ -136,29 +142,33 @@ def csv(string: str) -> list:
"--clipping",
default=False,
action="store_true",
help="when changing numeric controls, enforce the written value to be within allowed range (default: %(default)s)"
help="when changing numeric controls, enforce the written value to be within allowed range (default: %(default)s)",
)
parser.add_argument(
"--device",
type=str, default="0",
type=str,
default="0",
metavar="<dev>",
help="use device <dev> instead of /dev/video0; if <dev> starts with a digit, then /dev/video<dev> is used",
)
parser.add_argument(
"--get-ctrl",
type=csv, default=[],
type=csv,
default=[],
metavar="<ctrl>[,<ctrl>...]",
help="get the values of the specified controls",
)
parser.add_argument(
"--set-ctrl",
type=csv, default=[],
type=csv,
default=[],
metavar="<ctrl>=<val>[,<ctrl>=<val>...]",
help="set the values of the specified controls",
)
parser.add_argument(
"--reset-ctrl",
type=csv, default=[],
type=csv,
default=[],
metavar="<ctrl>[,<ctrl>...]",
help="reset the specified controls to their default values",
)
Expand Down
78 changes: 54 additions & 24 deletions v4l2py/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,11 @@ def __repr__(self):
if not self.is_flagged_write_only:
repr += f" value={self.value}"

flags = [flag.name.lower() for flag in ControlFlag if ((self._info.flags & flag) == flag)]
flags = [
flag.name.lower()
for flag in ControlFlag
if ((self._info.flags & flag) == flag)
]
if flags:
repr += " flags=" + ",".join(flags)

Expand Down Expand Up @@ -952,7 +956,9 @@ def value(self, value):
reasons.append("disabled")
if self.is_flagged_grabbed:
reasons.append("grabbed")
raise AttributeError(f"{self.__class__.__name__} {self.config_name} is not writeable: {', '.join(reasons)}")
raise AttributeError(
f"{self.__class__.__name__} {self.config_name} is not writeable: {', '.join(reasons)}"
)
v = self._convert_write(value)
v = self._mangle_write(v)
set_control(self.device, self.id, v)
Expand Down Expand Up @@ -995,28 +1001,38 @@ def is_flagged_has_payload(self) -> bool:

@property
def is_flagged_execute_on_write(self) -> bool:
return (self._info.flags & ControlFlag.EXECUTE_ON_WRITE) == ControlFlag.EXECUTE_ON_WRITE
return (
self._info.flags & ControlFlag.EXECUTE_ON_WRITE
) == ControlFlag.EXECUTE_ON_WRITE

@property
def is_flagged_modify_layout(self) -> bool:
return (self._info.flags & ControlFlag.MODIFY_LAYOUT) == ControlFlag.MODIFY_LAYOUT
return (
self._info.flags & ControlFlag.MODIFY_LAYOUT
) == ControlFlag.MODIFY_LAYOUT

@property
def is_flagged_dynamic_array(self) -> bool:
return (self._info.flags & ControlFlag.DYNAMIC_ARRAY) == ControlFlag.DYNAMIC_ARRAY
return (
self._info.flags & ControlFlag.DYNAMIC_ARRAY
) == ControlFlag.DYNAMIC_ARRAY

@property
def is_writeable(self) -> bool:
return not (self.is_flagged_read_only or self.is_flagged_inactive
or self.is_flagged_disabled or self.is_flagged_grabbed)
return not (
self.is_flagged_read_only
or self.is_flagged_inactive
or self.is_flagged_disabled
or self.is_flagged_grabbed
)

def set_to_default(self):
self.value = self.default


class BaseNumericControl(BaseControl):
lower_bound = -2 ** 31
upper_bound = 2 ** 31
lower_bound = -(2**31)
upper_bound = 2**31

def __init__(self, device, info, clipping=True):
super().__init__(device, info)
Expand All @@ -1026,9 +1042,13 @@ def __init__(self, device, info, clipping=True):
self.clipping = clipping

if self.minimum < self.lower_bound:
raise RuntimeWarning(f"Control {self.config_name}'s claimed minimum value {self.minimum} exceeds lower bound of {self.__class__.__name__}")
raise RuntimeWarning(
f"Control {self.config_name}'s claimed minimum value {self.minimum} exceeds lower bound of {self.__class__.__name__}"
)
if self.maximum > self.upper_bound:
raise RuntimeWarning(f"Control {self.config_name}'s claimed maximum value {self.maximum} exceeds upper bound of {self.__class__.__name__}")
raise RuntimeWarning(
f"Control {self.config_name}'s claimed maximum value {self.maximum} exceeds upper bound of {self.__class__.__name__}"
)

def _get_repr(self) -> str:
repr = f" min={self.minimum} max={self.maximum} step={self.step}"
Expand All @@ -1047,7 +1067,9 @@ def _convert_write(self, value):
pass
else:
return v
raise ValueError(f"Failed to coerce {value.__class__.__name__} '{value}' to int")
raise ValueError(
f"Failed to coerce {value.__class__.__name__} '{value}' to int"
)

def _mangle_write(self, value):
if self.clipping:
Expand All @@ -1057,16 +1079,20 @@ def _mangle_write(self, value):
return self.maximum
else:
if value < self.minimum:
raise ValueError(f"Control {self.config_name}: {value} exceeds allowed minimum {self.minimum}")
raise ValueError(
f"Control {self.config_name}: {value} exceeds allowed minimum {self.minimum}"
)
elif value > self.maximum:
raise ValueError(f"Control {self.config_name}: {value} exceeds allowed maximum {self.maximum}")
raise ValueError(
f"Control {self.config_name}: {value} exceeds allowed maximum {self.maximum}"
)
return value

def increase(self, steps: int = 1):
self.value += (steps * self.step)
self.value += steps * self.step

def decrease(self, steps: int = 1):
self.value -= (steps * self.step)
self.value -= steps * self.step

def set_to_minimum(self):
self.value = self.minimum
Expand All @@ -1076,13 +1102,13 @@ def set_to_maximum(self):


class IntegerControl(BaseNumericControl):
lower_bound = -2 ** 31
upper_bound = 2 ** 31
lower_bound = -(2**31)
upper_bound = 2**31
otaku42 marked this conversation as resolved.
Show resolved Hide resolved


class Integer64Control(BaseNumericControl):
lower_bound = -2 ** 63
upper_bound = 2 ** 63
lower_bound = -(2**63)
upper_bound = 2**63
otaku42 marked this conversation as resolved.
Show resolved Hide resolved


class BooleanControl(BaseControl):
Expand All @@ -1107,7 +1133,9 @@ def _convert_write(self, value):
pass
else:
return v
raise ValueError(f"Failed to coerce {value.__class__.__name__} '{value}' to bool")
raise ValueError(
f"Failed to coerce {value.__class__.__name__} '{value}' to bool"
)


class MenuControl(BaseControl, UserDict):
Expand All @@ -1126,7 +1154,9 @@ def __init__(self, device, info):
for item in iter_read_menu(self.device._fobj, self)
}
else:
raise TypeError(f"MenuControl only supports control types MENU or INTEGER_MENU, but not {self.type.name}")
raise TypeError(
f"MenuControl only supports control types MENU or INTEGER_MENU, but not {self.type.name}"
)

def _convert_write(self, value):
return int(value)
Expand Down Expand Up @@ -1186,10 +1216,10 @@ def is_disabled(self) -> bool:
return self.is_flagged_disabled

def increase(self, steps: int = 1):
self.value += (steps * self.step)
self.value += steps * self.step

def decrease(self, steps: int = 1):
self.value -= (steps * self.step)
self.value -= steps * self.step


class DeviceHelper:
Expand Down