See also the follow-up discussion on the CHDK forum and the addendum concerning partial downloading and PTP events.
One of the most desired features on the wish-list of the CHDK is remote capture for cameras have no native support for it. After making the CHDK port for the Canon IXUS 870 IS, I was also interested in adding this feature. However, a look at the firmware for relevant code quickly got rid of my motivation.
Later I realised that another feature I would like to see is access to all files when connecting the camera to a PC. Currently only specific files (e.g. JPGs) in a specific directory are shown. I had another look at the firmware and found some functions that implemented this behaviour. Unfortunately the code was a bit more complex than the simple file-extension checks I was hoping for. Again my motivation dissipated.
The common part of these two features is the Picture Transfer Protocol (PTP) that runs over USB (at least for the IXUS 870 IS, but I believe this is the case for most cameras). One thing I found during the previous code inspection were functions to add/remove handlers for PTP operations/events. Although this doesn't give me either of the two features above, I did recently realise that these functions could be used to extend the functionality available over USB in other more trivial directions.
What I've created and documented here is an interface to the CHDK using PTP that allows, amongst others, uploading/downloading of files, memory inspection and manipulation, remote function calls and various ways to shutdown or reboot. By no means is this meant to be a finished or even stable interface, but it has proven itself to be quite useful already (at least to me). I've also included a patch for libptp/ptpcam which provide a way to actually use the interface.
I hope this interface paves part of the road towards implementing the mentioned desired features and possibly many others. One thing that it already has done is allow me to find a way to enable record mode while a PTP connection is active, which, together with LUA script execution, already allows remote capture via the new interface (albeit a bit more cumbersome than it could be if the standard PTP operations are implemented). Especially the ability to inspect memory and call functions while the camera is running has been extremely helpful.
Picture Transfer Protocol (PTP)
In essence PTP is a pretty simple protocol. Besides events, all transactions consist of a Operation Request, an optional sending (x)or receiving of data (only one of the two per transaction) and finally a Response. Both Operation Request and Response are structured as follows:
Element | Bytes | Type |
---|---|---|
Code | 2 | unsigned integer |
Session ID | 4 | unsigned integer |
Transaction ID | 4 | unsigned integer |
Parameter 1 | 4 | |
Parameter 2 | 4 | |
Parameter 3 | 4 | |
Parameter 4 | 4 | |
Parameter 5 | 4 |
For the Operation Request the code should be an Operation Code (e.g. PTP_OC_OpenSession) and for the Response a Response Code (e.g. PTP_RC_OK). Unused parameters should be set to 0. Events are similar, but have only three parameters and are not followed by data or a response.
More details can be found in the ISO standards ISO 15740:2005 and ISO 15740:2008. The older PIMA 15740:2000 will probably also do just fine.
PTP in the firmware
There are mainly two pieces of interest in the firmware with respect to extending the PTP interface. One is the handler registration and the other is the functionality available in the handler to handle a transaction. Note that when I mention 'firmware' I mean the IXUS 870 IS firmware. Other cameras, especially those with DryOS, most likely have (very) similar code.
First we'll have a look at the registration. In the firmware there is a table of Operation Codes and handler functions (at 0xFFAE1ED4). Such a table can easily be found by searching for typical codes such as Canon extensions like 0x9021. This table is used in two places: a loop to add all handler and a loop to remove all handlers. Evidently, the functions used in these loops are, as I call them, add_ptp_handler (0xFF9EF744) and remove_ptp_handler (0xFF9EF838), respectively.
int add_ptp_handler(int opcode, ptp_handler f); |
int remove_ptp_handler(int opcode, ptp_handler f); |
The details of ptp_handler are discussed below. Note that add_ptp_handler replaces any previously set handler for the given Operation Code.
In both functions another (0xFF9EFD8C) is used to find an element in the build-up handlers list. This function is also used in one other place: the get_ptp_handler function (0xFF9EF904).
int get_ptp_handler(int opcode, ptp_handler *f); |
This function is useful to either check whether an Operation Code is already taken care of or, possibly more important, to get such a function in order to make a wrapper around it.
A final interesting function I found is initialise_ptp_handlers_data (0xFF9EF52C). This one initialises the data structure used by the other functions. Before it is called the others will return 0x17 to indicate that this data is missing. Multiple calls are fine. However, it seems that calling it to soon doesn't work. For this reason I've opted to create a task that repeatedly tries to call add_ptp_handler until it no longer returns 0x17.
Next we look at ptp_handler. Its definition is as follows.
typedef int (*ptp_handler)(int h, ptp_data *data, int opcode, int sess_id, int trans_id, int param1, int param2, int param3, int param4, int param5); |
Apart from h (meaning unknown) and data, these parameters correspond to the contents of the Operation Request. Note that the Operation Code is also included which means that you can have one handler to take care of multiple Operation Codes.
The ptp_data structure contains (amongst others) pointers to functions that are to be used to send/recv data/responses. Currently I've deduced the following contents at the beginning at the structure. More follows, but the meaning of it is unknown to me at this point.
int handle; |
int (*send_data)(int handle, char *buf, int size, int size_again, int, int, int); |
int (*recv_data)(int handle, char *buf, int size, int, int); |
int (*send_resp)(int handle, PTPContainer *resp); |
int (*get_data_size)(int handle); |
int (*send_err_resp)(int handle, PTPContainer *resp); |
Note that one should pass the handle value in this structure as the first argument to these functions. Also note that when making a wrapper for another handler function this structure allows you to pass on your own functions. This is particularly useful for debugging (e.g. just looking at the data that is passed or even simulating PTP transactions).
With send_resp one can send the PTP response in resp. Note, however, that PTPContainer differs somewhat from the actual response structure. First of all, the response code value is 4 bytes instead of 2. Secondly, before the parameter values there is an (int) element to indicate the number of used parameters. The latter is used by the USB transfer layer to only send as much as is actually needed.
In the A620 firmware I noticed that error responses to requests that normally have a data phase use the send_err_resp function instead of send_resp. I haven't seen this in the IXUS 870 IS firmware.
The send_data function is to send data in between the request and response. The arguments are not fully understood by me, but the typical usage seems to be send_data(h,buf,s,s,0,0,0), where buf contains the data to be sent and s is the size (in bytes) of that data. I suspect the extra arguments can be used to send data using multiple calls.
For recv_data pretty much the same holds. The last two arguments are typically 0. The difference is that before calling recv_data one must call get_data_size, which returns the total amount of data for this transaction. One must only call get_data_size once. Also, recv_data can be called multiple times to retrieve data in pieces. The size argument determines how much you get.
CHDK PTP extension
With the above I've added an extra operation PTP_OC_CHDK (value 0x9999) that allows the following operations (more detail on some of them in the appendix below).
- Shutdown/reboot; both soft and hard shutdowns as well as rebooting with or without using arbitrary firmware-update files
- Memory inspection and manipulation
- Remote function calling
- Retrieval of properties and parameter data
- Uploading/downloading of files (uploading sometimes mysteriously, but consistently, fails halfway, crashing the camera; might be related to size)
- Camera mode switching; allows switch to record mode to make pictures while PTP is running
- Execution of arbitrary LUA code
I've also made a patch to libptp and ptpcam. The former to reflect the above new operation and the latter to provide a simplistic command-line interface (ptpcam --chdk, type help for commands) to them. Using these new operations I've also added code (commented out) for easy update of CHDK binaries and inspection of camera log and task information.
Of course, the possible functionality that this kind of interface can provide is almost endless. I've just added those operations that where most useful/interesting to me at this point and can only strongly encourage further extensions.
Patches
- Patch for CHDK rev. 811 (IXUS 870 IS 1.01a only)
- Patch for libptp2-1.1.10
- Zip with patched libptp2-1.1.10 files for Windows (N.B.: make sure the paths in Makefile fit your configuration; requires libusb for Windows)
Appendix
In this appendix I discuss some functionality I've found that I haven't seen elsewhere yet. First of all the soft shutdown method. I'm always a bit annoyed when the camera is off but the lens is still sticking out (as is the result of a call to the CHDK shutdown method). After some failed attempts with, for example, the 'Bye' task, I figured I'd try to simulate a press of the power button. While attempting to set the power-button address (0xC02200F8 in my case) with things like 0x46 (using the PTP interface, of course), I found that the following does the trick.
<conn> set 0xC02200F8 0x4 |
Later I learned that PostLogicalEventForNotPowerType(0x1005) (PressOffButton) also works. This is probably a nicer solution.
Next up is the reboot with or without using arbitrary firmware-update files. At first I was just looking for a way to reboot using the default PS.FI2 file which led me to the function at 0xFFA296A0. Interestingly enough and something I hadn't realised before, the PS part isn't fixed; the function takes a filename as argument and used that to reboot to. For the part 'without', I noticed that the reboot method as used in the CHDK loader didn't work consistently. However, the above function also uses that method but first calls some other functions (specificly 0xFF94F7D8, 0xFF842480, 0xFF841594 and 0xFF8704F4). Doing the same proved to give a robust method for rebooting the camera as if it was just turned on.
Then the desired mode switching. There is already some work in this direction (e.g. here and here), but as far as I know not for DryOS cameras. I did find the previously used PB2Rec function (with a simple string search), but it contains a call to IsControlEventActive(0x10A5) that is prohibiting the switch from taking place. Just skipping that call did result in a switch, but also an immediate crash. Trying several combinations of functions in PB2Rec did allow me to find a way to avoid the crash, but then it just immediately switched back to playback mode. The most likely problem is that the same check is done in many other places.
I wasn't successful in finding a function to directly turn the event off, but with the remote-function-call functionality in the PTP interface was able to easily trace the main control flow of the IsControlEventActive(0x10A5) call and found that the event (0x10A5, a.k.a. ConnectUSBCable) was translated into 0x80000902 (by 0xFF9588EC). The following function (0xFF878954) showed that this is a code describing a state in bit table. The most significant bits are to indicate the activity of an event, respectively whether we are looking at a bit or a whole byte. The least significant bytes represent the array index (9), respectively the bit position at that index (2). The dual of 0x10A5, which is 0x10A6 (or DisconnectUSBCable), is translated to 0x902.
Using the address of the bit array (0x1ED44) I quickly found a method to change the bit array using the same codes at 0xFF878894. With this one can first deactivate the event (using 0x902), which essentially returns back control to the camera buttons, and then call PB2Rec to make the switch (successfully, this time). Note that the last call isn't really necessary because after the event deactivation you can simply press the shoot button to do the same. I have noticed, however, that the camera is still a bit confused (expectedly) and, for example, switching back to playback with the appropriate button results in a PTP disconnect. At this moment I just use both calls and to turn back to playback mode I reverse the process. This seems to work fine.
Finally the task information. I noticed the option to show tasks in the CHDK menu but that it didn't work for DryOS. In the work to make the PTP interface and finding various functionality I found some command-line utilities in the firmware. One of which is called extask (easy to find with string search) and prints task information.
The two most interesting functions in extask are get_task_lists (0xFF82288C) and get_task_info (0xFF813380).
int get_task_lists(int **ids, int **priorities); |
int get_task_info(int id, task_info *ti); |
With get_task_lists you can get an array of task identifiers in ids and an array of corresponding task priorities in priorities. The return value is the number of elements in the arrays. The arrays are sorted on priority.
To obtain the 48-byte task_info structure for a task one uses get_task_info. I've found the following elements in task_info.
Element | Bytes | |
---|---|---|
State | 4 | |
Priority | 4 | |
Unknown | 4 | |
Task function | 4 | |
Unknown | 4 | Optional pointer to char* with alternative name? |
Unknown | 8 | |
Unknown | 4 | Pointer to int in some table? |
Unknown | 8 | |
Unknown | 4 | Pointer to int? |
Task name | 4 | char* |
The state values have the following values (taken from 0xFF822848). The firmware distinguishes between RUNNING and READY in that RUNNING means that it is also the currently active process.
I haven't investigated this much further, but there is more to be found (e.g. stack information).