jim-easterbrook / python-gphoto2

Python interface to libgphoto2
GNU Lesser General Public License v3.0
366 stars 59 forks source link

gphoto2.GPhoto2Error: [-2] Bad parameters #134

Closed eechhx closed 2 years ago

eechhx commented 2 years ago

Your system What version of Python are you using?

python3 --version
Python 3.8.10

What version of libgphoto2 have you installed?

gphoto2 -v
gphoto2 2.5.28

Copyright (c) 2000-2021 Marcus Meissner and others

gphoto2 comes with NO WARRANTY, to the extent permitted by law. You may
redistribute copies of gphoto2 under the terms of the GNU General Public
License. For more information about these matters, see the files named COPYING.

This version of gphoto2 is using the following software versions and options:
gphoto2         2.5.28         gcc, popt(m), exif, no cdk, no aa, jpeg, no readline
libgphoto2      2.5.28         standard camlibs (SKIPPING docupen), gcc, no ltdl, EXIF
libgphoto2_port 0.12.0         iolibs: disk ptpip serial usb1 usbdiskdirect usbscsi, gcc, no ltdl, EXIF, USB, serial without locking

How have you installed (or attempted to install) python-gphoto2?

sudo pip3 install gphoto2  ### I'm on version 2.3.2 now!

Your problem Hello Jim, sorry I'm back with another issue so shortly after we just closed my previous one. I get the error gphoto2.GPhoto2Error: [-2] Bad parameters when trying to access _CameraWidget * pointer methods, but only if I call the Camera.get_config() more than once.

Train of Thought If I wanted to modify a camera property's value, I would like to check if it updated successfully. For example, I would first find the property by ID with _CameraWidget.get_child_by_id() and then assuming it passes error checks and it's not readonly, I would update the value with _CameraWidget.set_value(). However, sometimes you're able to successfully modify a value (error checking returns 0), but in actuality the camera property doesn't actually change on (i.e. it was physically set to a different mode). Getting the camera summary again shows that it doesn't actually update. I've been passing around my initial config from Camera.get_config() so checking that value shows that it supposedly did successfully update - but we know that's not the case.

Error Recreation

import gphoto2 as gp

camera = gp.Camera()
camera.init()

config = camera.get_config()
print("Camera get_config method call 1", config)
property = config.get_child_by_name("whitebalance")
print("Property value in main", property.get_name())
config = camera.get_config()
print("Camera get_config method call 2", config)
property = config.get_child_by_id(20)
print("Property value in main", property.get_name())
Camera get_config method call 1 <Swig Object of type '_CameraWidget *' at 0x7f58cfbaa0b0>
Property value in main whitebalance
Camera get_config method call 2 <Swig Object of type '_CameraWidget *' at 0x7f58cfbaa1f0>
Traceback (most recent call last):
  File "error_test.py", line 12, in <module>
    property = config.get_child_by_id(20)
gphoto2.GPhoto2Error: [-2] Bad parameters

In the above example, we see it can successfully get the camera property via its name, and running _CameraWidget.get_name() method validates this. However the error is thrown when trying to later get the property by ID, with the _CameraWidget.get_child_by_id() method.

However, calling get_config() multiple times, and getting the property by the same previous method throws no errors.

import gphoto2 as gp

camera = gp.Camera()
camera.init()

config = camera.get_config()
print("Camera get_config method call 1", config)
property = config.get_child_by_name("whitebalance")
print("Property value in main", property.get_name())
config = camera.get_config()
print("Camera get_config method call 2", config)
property = config.get_child_by_name("whitebalance")
print("Property value in main", property.get_name())
Camera get_config method call 1 <Swig Object of type '_CameraWidget *' at 0x7fdb4551e230>
Property value in main whitebalance
Camera get_config method call 2 <Swig Object of type '_CameraWidget *' at 0x7fdb4551e370>
Property value in main whitebalance

And when calling get_config() just once, things work as expected:

import gphoto2 as gp

camera = gp.Camera()
camera.init()

config = camera.get_config()
print("Camera get_config method call 1", config)
property = config.get_child_by_name("whitebalance")
print("Property value by name", property.get_name())
property = config.get_child_by_id(20)
print("Property value by id", property.get_name())
Camera get_config method call 1 <Swig Object of type '_CameraWidget *' at 0x7fee6adc3170>
Property value by name whitebalance
Property value by id whitebalance

And the workaround seems to be just reinitializing the Camera * object.

import gphoto2 as gp

camera = gp.Camera()
camera.init()
config = camera.get_config()
print("Camera get_config method call 1", config)
property = config.get_child_by_name("whitebalance")
print("Property value by name", property.get_name())

camera = gp.Camera()
camera.init()
config = camera.get_config()
print("Camera get_config method call 2", config)
property = config.get_child_by_name("whitebalance")
print("Property value by name", property.get_name())

camera = gp.Camera()
camera.init()
config = camera.get_config()
print("Camera get_config method call 3", config)
property = config.get_child_by_name("whitebalance")
print("Property value by name", property.get_name())
property = config.get_child_by_id(20)
print("Property value by id", property.get_name())
Camera get_config method call 1 <Swig Object of type '_CameraWidget *' at 0x7ff26f4e31f0>
Property value by name whitebalance
Camera get_config method call 2 <Swig Object of type '_CameraWidget *' at 0x7ff26f4e32b0>
Property value by name whitebalance
Camera get_config method call 3 <Swig Object of type '_CameraWidget *' at 0x7ff26f4e3330>
Property value by name whitebalance
Property value by id whitebalance

Please advise and let me know if my interpretation in the use of methods is invalid, or this is behaving as expected. Thank you for your time once again.

eechhx commented 2 years ago

And similar results from using just the SWIG interface methods. Pardon my lack of error checking in the example below.

import gphoto2 as gp
import time

camera = gp.check_result(gp.gp_camera_new())
context = gp.gp_context_new()
config = gp.check_result(gp.gp_camera_get_config(camera, context))

prop = config.get_child_by_id(20)
print("prop by id", prop.get_name())
prop = config.get_child_by_name("whitebalance")
print("prop by name", prop.get_name())

config = gp.check_result(gp.gp_camera_get_config(camera, context))
prop = config.get_child_by_id(20)
print("prop by id", prop.get_name())
prop = config.get_child_by_name("whitebalance")
print("prop by name", prop.get_name())
prop by id whitebalance
prop by name whitebalance
Traceback (most recent call last):
  File "error_test.py", line 14, in <module>
    prop = config.get_child_by_id(20)
gphoto2.GPhoto2Error: [-2] Bad parameters

But when reinitializing the camera objects, no exceptions are thrown.

import gphoto2 as gp
import time

camera = gp.check_result(gp.gp_camera_new())
context = gp.gp_context_new()
config = gp.check_result(gp.gp_camera_get_config(camera, context))

prop = config.get_child_by_id(20)
print("prop by id", prop.get_name())
prop = config.get_child_by_name("whitebalance")
print("prop by name", prop.get_name())

camera = gp.check_result(gp.gp_camera_new())
context = gp.gp_context_new()
config = gp.check_result(gp.gp_camera_get_config(camera, context))

prop = config.get_child_by_id(20)
print("prop by id", prop.get_name())
prop = config.get_child_by_name("whitebalance")
print("prop by name", prop.get_name())
prop by id whitebalance
prop by name whitebalance
prop by id whitebalance
prop by name whitebalance
eechhx commented 2 years ago

However the workaround is only useful when you're reinitializing the camera within the same scope. Reinitializing fails when you make function calls (which makes sense).

import gphoto2 as gp

def test_func():
    camera = gp.Camera()
    camera.init()
    config = camera.get_config()

camera = gp.Camera()
camera.init()
config = camera.get_config()

test_func()
Traceback (most recent call last):
  File "error_test.py", line 14, in <module>
    test_func()
  File "error_test.py", line 6, in test_func
    camera.init()
gphoto2.GPhoto2Error: [-60] Could not lock the device

Thus, I'm not sure if the workaround is ideal. (But you can always wrap everything up as a class!)

jim-easterbrook commented 2 years ago

I'm not sure if calling get_config twice is supported, or why you'd want to do it. The first time you call it all the camera's config values are copied to a tree of "widgets", calling it again will just copy the same values to another tree of widgets. Setting a value on one tree won't affect the other, and won't affect the camera until you call set_config to copy the (changed) values back to the camera.

After calling set_config you might need to delete all references to the config widgets before calling get_config again, but I'm not sure.

However, the Canon config stuff has changed in the latest release of libgphoto2, so you might have found a bug. Try going back to python-gphoto2==2.3.0 and see what happens. https://github.com/gphoto/libgphoto2/issues/352#issuecomment-933401683

eechhx commented 2 years ago

However, the Canon config stuff has changed in the latest release of libgphoto2, so you might have found a bug. Try going back to python-gphoto2==2.3.0 and see what happens.

Same error outputs as gphoto2==2.3.2 listed above.

I'm not sure if calling get_config twice is supported, or why you'd want to do it.

i.e. - the reason why I would call get_config() twice is because despite set_config() passing error checks and returning 0, if the camera is physically set in a different mode (AUTO), attempting to set white balance shows the value to be set correctly after setting the config and calling get_value() . However we know that's not the case due to the camera being in a specific mode. I would just like to account for these situations, which is why I call to get updated configs as a sanity check.

import gphoto2 as gp
import time

camera = gp.Camera()
camera.init()
config = camera.get_config()

property = config.get_child_by_name("whitebalance")
print("White balance readonly value", property.get_readonly())
print("White balance value currently", property.get_value())
property.set_value("Daylight")
camera.set_config(config)
print("White balance value after setting", property.get_value())

camera = gp.Camera()
camera.init()
config = camera.get_config()

property = config.get_child_by_name("whitebalance")
print("White balance value after reinit", property.get_value())
White balance readonly value 0
White balance value currently Automatic
White balance value after setting Daylight
White balance value after reinit Automatic

After calling set_config you might need to delete all references to the config widgets before calling get_config again, but I'm not sure.

The error comes from switching how you get a child property. As to why someone would want to do this, who knows. But my assumption would be that it should be fairly dynamic. Prior to the del call, if you call get_child_by_name() and then get_child_by_name() or get_child_by_label(), it passes the test case. However if you call get_child_by_name() and then attempt to call get_child_by_id(), an exception is raised. This is the behaviour I'm a bit confused about.

Calling get_child_by_name() and then get_child_by_id()

camera = gp.Camera()
camera.init()
config = camera.get_config()

property = config.get_child_by_name("whitebalance")
print("White balance readonly value", property.get_readonly())
print("White balance value currently", property.get_value())
property.set_value("Daylight")
camera.set_config(config)
print("White balance value after setting", property.get_value())

del config
config = camera.get_config()
property = config.get_child_by_id(20)
print("White balance value after reinit", property.get_value())
White balance readonly value 0
White balance value currently Automatic
White balance value after setting Daylight
Traceback (most recent call last):
  File "error_test.py", line 18, in <module>
    property = config.get_child_by_id(20)
gphoto2.GPhoto2Error: [-2] Bad parameters

Calling get_child_by_id() and then get_child_by_name()

# Camera initialization not shown
property = config.get_child_by_id(20)
print("White balance readonly value", property.get_readonly())
print("White balance value currently", property.get_value())
property.set_value("Daylight")
camera.set_config(config)
print("White balance value after setting", property.get_value())

del config
config = camera.get_config()
property = config.get_child_by_name("whitebalance")
print("White balance value after reinit", property.get_value())
White balance readonly value 0
White balance value currently Automatic
White balance value after setting Daylight
White balance value after reinit Automatic

Calling get_child_by_id() and then get_child_by_label()

# Camera initialization not shown
property = config.get_child_by_id(20)
print("White balance readonly value", property.get_readonly())
print("White balance value currently", property.get_value())
property.set_value("Daylight")
camera.set_config(config)
print("White balance value after setting", property.get_value())

del config
config = camera.get_config()
property = config.get_child_by_label("WhiteBalance")
print("White balance value after reinit", property.get_value())
White balance readonly value 0
White balance value currently Automatic
White balance value after setting Daylight
White balance value after reinit Automatic

Calling get_child_by_label() and then get_child_by_id()

# Camera initialization not shown
property = config.get_child_by_label("WhiteBalance")
print("White balance readonly value", property.get_readonly())
print("White balance value currently", property.get_value())
property.set_value("Daylight")
camera.set_config(config)
print("White balance value after setting", property.get_value())

del config
config = camera.get_config()
property = config.get_child_by_id(20)
print("White balance value after reinit", property.get_value())
White balance readonly value 0
White balance value currently Automatic
White balance value after setting Daylight
Traceback (most recent call last):
  File "error_test.py", line 18, in <module>
    property = config.get_child_by_id(20)
gphoto2.GPhoto2Error: [-2] Bad parameters

Calling get_child_by_id() and then get_child_by_id()

# Camera initialization not shown
property = config.get_child_by_id(20)
print("White balance readonly value", property.get_readonly())
print("White balance value currently", property.get_value())
property.set_value("Daylight")
camera.set_config(config)
print("White balance value after setting", property.get_value())

del config
config = camera.get_config()
property = config.get_child_by_id(20)
print("White balance value after reinit", property.get_value())
White balance readonly value 0
White balance value currently Automatic
White balance value after setting Daylight
Traceback (most recent call last):
  File "error_test.py", line 18, in <module>
    property = config.get_child_by_id(20)
gphoto2.GPhoto2Error: [-2] Bad parameters

Note that the del config is not necessary for the test cases which pass. Something's off about calling get_child_by_id() a second time without reinitializing camera. Perhaps this is more of a libgphoto2 discussion, but I haven't had the time to take a look at the C source. Feel free to close if you feel this is not relevant to python-gphoto2!

jim-easterbrook commented 2 years ago

You need to delete config and property (and any other widgets). Every widget keeps a reference to the root of its tree, so the tree is only deleted when all its widgets have been deleted. (This avoids segmentation faults if you carry on using a widget after the root widget has been deleted.)

Your initial examples didn't include set_config so I misunderstood what you are doing. Getting confirmation is an entirely reasonable thing to try.

eechhx commented 2 years ago

You need to delete config and property (and any other widgets).

import gphoto2 as gp
import time

camera = gp.Camera()
camera.init()
config = camera.get_config()
print(config)

property = config.get_child_by_id(20)
print("White balance readonly value", property.get_readonly())
print("White balance value currently", property.get_value())

property.set_value("Daylight")
camera.set_config(config)
print("White balance value after setting", property.get_value())

config_two = camera.get_config()
print(config_two)
### Reference Check
print("config is config_two", config is config_two)

property_two = config_two.get_child_by_id(20)
print("White balance value after reinit", property_two.get_value())
<Swig Object of type '_CameraWidget *' at 0x7f60820fc370>
White balance readonly value 0
White balance value currently Automatic
White balance value after setting Daylight
<Swig Object of type '_CameraWidget *' at 0x7f60820fc4f0>
config is config_two False
Traceback (most recent call last):
  File "error_test.py", line 21, in <module>
    property_two = config_two.get_child_by_id(20)
gphoto2.GPhoto2Error: [-2] Bad parameters

Please correct me if I'm wrong, but the only references to widgets are config and property? Python has garbage collection handled, so users should not need to touch garbage collection. As far as I know, calling del is unnecessary.

Your initial examples didn't include set_config so I misunderstood what you are doing. Getting confirmation is an entirely reasonable thing to try.

No worries, that's my fault for not being more clear.

jim-easterbrook commented 2 years ago

My point is that config and property are Python objects both pointing to a C structure (the tree of widgets) which will only be deleted when config and property are both deleted. I don't know how the internals of libgphoto2 work, but I thought a possible explanation for your problem might be having two config trees active at the same time. The fact that reinitialising the camera helps suggests things are being cached or otherwise held in memory.

Garbage collection is irrelevant here. It's to do with reference cycles (e.g. deleted objects A & B each have a reference to the other).

eechhx commented 2 years ago

Yeah it's just interesting that it only happens with a call to get_child_by_id() a second time, but not with the other functions. Regardless the reinitialization is not a big deal on my part. Feel free to close the issue and thank you for your time once again.

jim-easterbrook commented 2 years ago

Try getting property_two by label and then getting its id. The documentation says get_id "retrieves the unique id of the CameraWidget" - maybe it's truly unique and not repeated in the second config tree.

I've never played with the widget ids so don't know what they're for.

eechhx commented 2 years ago

Oops just saw this. Think we posted at the same time.

Try getting property_two by label and then getting its id. The documentation says get_id "retrieves the unique id of the CameraWidget" - maybe it's truly unique and not repeated in the second config tree.

Great suggestion. So I guess IDs are dynamic and not necessarily consistent after calling get_config() a second time. But upon reinitialization it gets reset back to its original ID value.

import gphoto2 as gp
import time

camera = gp.Camera()
camera.init()
config = camera.get_config()

property = config.get_child_by_name("whitebalance")
print("Property ID", property.get_id())
print("White balance readonly value", property.get_readonly())
print("White balance value currently", property.get_value())
property.set_value("Daylight")
camera.set_config(config)
print("White balance value after setting", property.get_value())

config_two = camera.get_config()
property_two = config_two.get_child_by_name("whitebalance")
print("Property two ID", property_two.get_id())
print("Property two name", property.get_name())
print("Property two label", property.get_label())

# Property ID went from 20 to 93.
# Error is thrown here
property_two = config_two.get_child_by_id(20)
print("White balance value after reinit", property_two.get_value())
Property ID 20
White balance readonly value 0
White balance value currently Automatic
White balance value after setting Daylight
Property two ID 93
Property two name whitebalance
Property two label WhiteBalance
Traceback (most recent call last):
  File "error_test.py", line 24, in <module>
    property_two = config_two.get_child_by_id(20)
gphoto2.GPhoto2Error: [-2] Bad parameters

Reinitialization

import gphoto2 as gp
import time

camera = gp.Camera()
camera.init()
config = camera.get_config()

property = config.get_child_by_name("whitebalance")
print("Property ID", property.get_id())
print("White balance readonly value", property.get_readonly())
print("White balance value currently", property.get_value())
property.set_value("Daylight")
camera.set_config(config)
print("White balance value after setting", property.get_value())

config_two = camera.get_config()
property_two = config_two.get_child_by_name("whitebalance")
print("Property two ID", property_two.get_id())
print("Property two name", property.get_name())
print("Property two label", property.get_label())

camera = gp.Camera()
camera.init()
config_three = camera.get_config()
property_three = config_three.get_child_by_id(20)
print("Property three ID", property_three.get_id())
print("Property three name", property_three.get_name())
print("Property three label", property_three.get_label())
Property ID 20
White balance readonly value 0
White balance value currently Automatic
White balance value after setting Daylight
Property two ID 93
Property two name whitebalance
Property two label WhiteBalance
Property three ID 20
Property three name whitebalance
Property three label WhiteBalance
jim-easterbrook commented 2 years ago

That confirms my supposition rather nicely. If the id is 93 then 20 is an invalid parameter for get_child_by_id. I think get_child_by_id is of little use in user programs. Getting widgets by name is probably safest - the label may vary with localisation, or even be duplicated in the widget tree.

If you haven't already tried it, the camera-config-gui.py example can be used to explore the widget tree.

eechhx commented 2 years ago

Original implementation utilizesget_child_by_id() for the sake of being dynamic in case child names vary between camera manufacturers. However, I currently don't have a wide array of cameras to test against to validate this original thought process. Regardless I'll just access widgets by name for now. And yes camera-config-gui.py is very handy for widget tree exploration - thank you!

I'll be closing the issue now, so thanks for the support once again.

jim-easterbrook commented 2 years ago

If the names vary the ids will vary even more! Config trees vary hugely, from a flat list of all the widgets to a multi tab tree of groups of related widgets. Writing anything camera independent will be a challenge. (Downloading pictures, which is my main use of libgphoto2, is much more straightforward. I only added the widget stuff to python-gphoto2 for completeness. I've never made much use of it.)

jim-easterbrook commented 2 years ago

PS I think the names are "invented" by libgphoto2's authors, possibly with reference to camera makers' software. Binary addresses are all that travels over USB. Some of the widgets on my cameras just have hexadecimal number names, probably because their function hasn't yet been identified by reverse engineering.