source: trunk/core/ptp.c @ 1316

Revision 1316, 15.0 KB checked in by philmoz, 3 years ago (diff)

PTP 'Live View' extensions.
Based on mweerden's original work - https://github.com/mweerden/CHDKPTPRemote/tree/remote-preview
Tested on G12, SX30, SX130IS and IXUS310.
Sample .Net client code - http://chdk.setepontos.com/index.php?topic=4338.msg72684#msg72684
Pre-compiled sample client - http://chdk.setepontos.com/index.php?topic=4338.msg72684#msg72684
Sample client is in the early stages and still needs work.

Line 
1#include "camera.h"
2#ifdef CAM_CHDK_PTP
3#include "stddef.h"
4#include "platform.h"
5#include "stdlib.h"
6#include "ptp.h"
7#include "kbd.h"
8
9#include "core.h"
10 
11static int buf_size=0;
12
13#ifdef OPT_LUA
14#include "script.h"
15#include "action_stack.h"
16// process id for scripts, increments before each attempt to run script
17// does not handle wraparound
18static unsigned script_run_id;
19#endif
20
21static int handle_ptp(
22                int h, ptp_data *data, int opcode, int sess_id, int trans_id,
23                int param1, int param2, int param3, int param4, int param5);
24
25void init_chdk_ptp()
26{
27  int r;
28 
29  // wait until ptp_handlers_info is initialised and add CHDK PTP interface
30  r = 0x17;
31  while ( r==0x17 )
32  {
33    r = add_ptp_handler(PTP_OC_CHDK,handle_ptp,0);
34    msleep(250);
35  }
36
37  ExitTask();
38}
39
40static int recv_ptp_data(ptp_data *data, char *buf, int size)
41  // repeated calls per transaction are ok
42{
43  while ( size >= buf_size )
44  {
45    data->recv_data(data->handle,buf,buf_size,0,0);
46    // XXX check for success??
47
48    size -= buf_size;
49    buf += buf_size;
50  }
51  if ( size != 0 )
52  {
53    data->recv_data(data->handle,buf,size,0,0);
54    // XXX check for success??
55  }
56
57  return 1;
58}
59
60// camera will shut down if you ignore a recv data phase
61static void flush_recv_ptp_data(ptp_data *data,int size) {
62  char *buf;
63  buf = malloc((size > buf_size) ? buf_size:size);
64  if(!buf) // buf_size should always be less then available memory
65    return;
66  while ( size > 0 )
67  {
68    if ( size >= buf_size )
69    {
70      recv_ptp_data(data,buf,buf_size);
71      size -= buf_size;
72    } else {
73      recv_ptp_data(data,buf,size);
74      size = 0;
75    }
76  }
77  free(buf);
78}
79
80static int send_ptp_data(ptp_data *data, const char *buf, int size)
81  // repeated calls per transaction are *not* ok
82{
83  int tmpsize;
84 
85  tmpsize = size;
86  while ( size >= buf_size )
87  {
88    if ( data->send_data(data->handle,buf,buf_size,tmpsize,0,0,0) )
89    {
90      return 0;
91    }
92
93    tmpsize = 0;
94    size -= buf_size;
95    buf += buf_size;
96  }
97  if ( size != 0 )
98  {
99    if ( data->send_data(data->handle,buf,size,tmpsize,0,0,0) )
100    {
101      return 0;
102    }
103  }
104
105  return 1;
106}
107
108#ifdef OPT_LUA
109// TODO this could be a generic ring buffer of words
110#define PTP_SCRIPT_MSG_Q_LEN 16
111typedef struct {
112  unsigned r; // index of current read value
113  unsigned w; // index of next write value, if w == r, empty TODO "full" currently wastes a slot
114  ptp_script_msg *q[PTP_SCRIPT_MSG_Q_LEN];
115} ptp_script_msg_q;
116
117// TODO it would be better to allocate these only when needed
118ptp_script_msg_q msg_q_in; // messages to pc from script
119ptp_script_msg_q msg_q_out; // messages to script from pc
120
121unsigned script_msg_q_next(unsigned i) {
122  if(i == PTP_SCRIPT_MSG_Q_LEN - 1) {
123    return 0;
124  }
125  return i+1;
126}
127
128unsigned script_msg_q_full(ptp_script_msg_q *q) {
129  return (script_msg_q_next(q->w) == q->r);
130}
131
132unsigned script_msg_q_empty(ptp_script_msg_q *q) {
133  return (q->w == q->r);
134}
135
136int enqueue_script_msg(ptp_script_msg_q *q,ptp_script_msg *msg) {
137  unsigned w = script_msg_q_next(q->w);
138  if(w == q->r) {
139    return 0;
140  }
141  if(msg == NULL) {
142    return 0;
143  }
144  q->q[q->w] = msg;
145  q->w = w;
146  return 1;
147}
148
149ptp_script_msg* dequeue_script_msg(ptp_script_msg_q *q) {
150  ptp_script_msg *msg;
151  if(script_msg_q_empty(q)) {
152    return NULL;
153  }
154  msg = q->q[q->r];
155  q->r = script_msg_q_next(q->r);
156  return msg;
157}
158
159// public interface for script
160// create a message to be queued later
161ptp_script_msg* ptp_script_create_msg(unsigned type, unsigned subtype, unsigned datasize, const void *data) {
162  ptp_script_msg *msg;
163  msg = malloc(sizeof(ptp_script_msg) + datasize);
164  msg->size = datasize;
165  msg->type = type;
166  msg->subtype = subtype;
167  // caller may fill in data themselves
168  // datasize may be empty (e.g. empty string)
169  if(data && datasize) {
170      memcpy(msg->data,data,msg->size);
171  }
172  return msg;
173}
174
175// add a message to the outgoing queue
176int ptp_script_write_msg(ptp_script_msg *msg) {
177  msg->script_id = script_run_id;
178  return enqueue_script_msg(&msg_q_out,msg);
179}
180
181// retrieve the next message in the incoming queue
182ptp_script_msg* ptp_script_read_msg(void) {
183  ptp_script_msg *msg;
184  while(1) {
185    msg = dequeue_script_msg(&msg_q_in);
186    // no messages
187    if(!msg) {
188        return NULL;
189    }
190    // does message belong to our script
191    if(!msg->script_id || msg->script_id == script_run_id) {
192      return msg;
193    } else {
194    // no: discard and keep looking
195      free(msg);
196    }
197  }
198}
199
200// convenience function write an error message
201int ptp_script_write_error_msg(unsigned errtype, const char *err) {
202  if(script_msg_q_full(&msg_q_out)) {
203    return 0;
204  }
205  ptp_script_msg *msg = ptp_script_create_msg(PTP_CHDK_S_MSGTYPE_ERR,errtype,strlen(err),err);
206  if(!msg) {
207    return 0;
208  }
209  return ptp_script_write_msg(msg);
210}
211
212#endif
213
214static int handle_ptp(
215               int h, ptp_data *data, int opcode, int sess_id, int trans_id,
216               int param1, int param2, int param3, int param4, int param5)
217{
218  static union {
219    char *str;
220  } temp_data;
221  static int temp_data_kind = 0; // 0: nothing, 1: ascii string
222  static int temp_data_extra; // size (ascii string)
223  PTPContainer ptp;
224
225  // initialise default response
226  memset(&ptp,0,sizeof(PTPContainer));
227  ptp.code = PTP_RC_OK;
228  ptp.sess_id = sess_id;
229  ptp.trans_id = trans_id;
230  ptp.num_param = 0;
231 
232  // TODO calling this on every PTP command is not good,
233  // since it figures out free memory by repeatedly malloc'ing!
234  buf_size=core_get_free_memory()>>1;
235
236  // handle command
237  switch ( param1 )
238  {
239
240    case PTP_CHDK_Version:
241      ptp.num_param = 2;
242      ptp.param1 = PTP_CHDK_VERSION_MAJOR;
243      ptp.param2 = PTP_CHDK_VERSION_MINOR;
244      break;
245    case PTP_CHDK_ScriptSupport:
246      ptp.num_param = 1;
247      ptp.param1 = 0;
248#ifdef OPT_LUA
249      ptp.param1 |= PTP_CHDK_SCRIPT_SUPPORT_LUA;
250#endif
251      break;
252    case PTP_CHDK_ScriptStatus:
253      ptp.num_param = 1;
254// TODO script_is_running should always be defined, just ret 0 if script disabled
255      ptp.param1 = 0;
256#ifdef OPT_SCRIPTING
257      ptp.param1 |= script_is_running()?PTP_CHDK_SCRIPT_STATUS_RUN:0;
258#ifdef OPT_LUA
259      ptp.param1 |= (!script_msg_q_empty(&msg_q_out))?PTP_CHDK_SCRIPT_STATUS_MSG:0;
260#endif
261#endif
262      break;
263    case PTP_CHDK_GetMemory:
264      if ( param2 == 0 || param3 < 1 ) // null pointer or invalid size?
265      {
266        ptp.code = PTP_RC_GeneralError;
267        break;
268      }
269
270      if ( !send_ptp_data(data,(char *) param2,param3) )
271      {
272        ptp.code = PTP_RC_GeneralError;
273      }
274      break;
275     
276    case PTP_CHDK_SetMemory:
277      if ( param2 == 0 || param3 < 1 ) // null pointer or invalid size?
278      {
279        ptp.code = PTP_RC_GeneralError;
280        break;
281      }
282
283      data->get_data_size(data->handle); // XXX required call before receiving
284      if ( !recv_ptp_data(data,(char *) param2,param3) )
285      {
286        ptp.code = PTP_RC_GeneralError;
287      }
288      break;
289
290    case PTP_CHDK_CallFunction:
291      if ( (param2 & 0x1) == 0 )
292      {
293        int s;
294        int *buf = (int *) malloc((10+1)*sizeof(int));
295
296        if ( buf == NULL )
297        {
298          ptp.code = PTP_RC_GeneralError;
299          break;
300        }
301
302        s = data->get_data_size(data->handle);
303        if ( !recv_ptp_data(data,(char *) buf,s) )
304        {
305          ptp.code = PTP_RC_GeneralError;
306          break;
307        }
308
309        ptp.num_param = 1;
310        ptp.param1 = ((int (*)(int,int,int,int,int,int,int,int,int,int)) buf[0])(buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],buf[8],buf[9],buf[10]);
311
312        free(buf);
313        break;
314      } else { // if ( (param2 & 0x1) != 0 )
315        ptp.num_param = 1;
316        ptp.param1 = ((int (*)(ptp_data*,int,int)) param3)(data,param4,param5);
317        break;
318      }
319
320    case PTP_CHDK_TempData:
321      if ( param2 & PTP_CHDK_TD_DOWNLOAD )
322      {
323        const char *s = NULL;
324        size_t l = 0;
325
326        if ( temp_data_kind == 0 )
327        {
328          ptp.code = PTP_RC_GeneralError;
329          break;
330        }
331
332        if ( temp_data_kind == 1 )
333        {
334          s = temp_data.str;
335          l = temp_data_extra;
336        }
337
338        if ( !send_ptp_data(data,s,l) )
339        {
340          ptp.code = PTP_RC_GeneralError;
341          break;
342        }
343       
344      } else if ( ! (param2 & PTP_CHDK_TD_CLEAR) ) {
345        if ( temp_data_kind == 1 )
346        {
347          free(temp_data.str);
348        }
349        temp_data_kind = 0;
350
351        temp_data_extra = data->get_data_size(data->handle);
352
353        temp_data.str = (char *) malloc(temp_data_extra);
354        if ( temp_data.str == NULL )
355        {
356          ptp.code = PTP_RC_GeneralError;
357          break;
358        }
359
360        if ( !recv_ptp_data(data,temp_data.str,temp_data_extra) )
361        {
362          ptp.code = PTP_RC_GeneralError;
363          break;
364        }
365        temp_data_kind = 1;
366      }
367      if ( param2 & PTP_CHDK_TD_CLEAR )
368      {
369        if ( temp_data_kind == 1 )
370        {
371          free(temp_data.str);
372        }
373        temp_data_kind = 0;
374      }
375      break;
376
377    case PTP_CHDK_UploadFile:
378      {
379        FILE *f;
380        int s,r,fn_len;
381        char *buf, *fn;
382
383        s = data->get_data_size(data->handle);
384
385        recv_ptp_data(data,(char *) &fn_len,4);
386        s -= 4;
387
388        fn = (char *) malloc(fn_len+1);
389        if ( fn == NULL )
390        {
391          ptp.code = PTP_RC_GeneralError;
392          break;
393        }
394        fn[fn_len] = '\0';
395
396        recv_ptp_data(data,fn,fn_len);
397        s -= fn_len;
398
399        f = fopen(fn,"wb");
400        if ( f == NULL )
401        {
402          ptp.code = PTP_RC_GeneralError;
403          free(fn);
404          break;
405        }
406        free(fn);
407
408        buf = (char *) malloc(buf_size);
409        if ( buf == NULL )
410        {
411          ptp.code = PTP_RC_GeneralError;
412          break;
413        }
414        while ( s > 0 )
415        {
416          if ( s >= buf_size )
417          {
418            recv_ptp_data(data,buf,buf_size);
419            fwrite(buf,1,buf_size,f);
420            s -= buf_size;
421          } else {
422            recv_ptp_data(data,buf,s);
423            fwrite(buf,1,s,f);
424            s = 0;
425          }
426        }
427
428        fclose(f);
429
430        free(buf);
431        break;
432      }
433     
434    case PTP_CHDK_DownloadFile:
435      {
436        FILE *f;
437        int tmp,t,s,r,fn_len;
438        char *buf, *fn;
439
440        if ( temp_data_kind != 1 )
441        {
442          // send dummy data, otherwise error hoses connection
443          send_ptp_data(data,"\0",1);
444          ptp.code = PTP_RC_GeneralError;
445          break;
446        }
447
448        fn = (char *) malloc(temp_data_extra+1);
449        if ( fn == NULL )
450        {
451          // send dummy data, otherwise error hoses connection
452          send_ptp_data(data,"\0",1);
453          free(temp_data.str);
454          temp_data_kind = 0;
455          ptp.code = PTP_RC_GeneralError;
456          break;
457        }
458        memcpy(fn,temp_data.str,temp_data_extra);
459        fn[temp_data_extra] = '\0';
460
461        free(temp_data.str);
462        temp_data_kind = 0;
463
464        f = fopen(fn,"rb");
465        if ( f == NULL )
466        {
467          // send dummy data, otherwise error hoses connection
468          send_ptp_data(data,"\0",1);
469          ptp.code = PTP_RC_GeneralError;
470          free(fn);
471          break;
472        }
473        free(fn);
474
475        fseek(f,0,SEEK_END);
476        s = ftell(f);
477        fseek(f,0,SEEK_SET);
478
479        buf = (char *) malloc(buf_size);
480        if ( buf == NULL )
481        {
482          // send dummy data, otherwise error hoses connection
483          send_ptp_data(data,"\0",1);
484          ptp.code = PTP_RC_GeneralError;
485          break;
486        }
487
488        tmp = s;
489        t = s;
490        while ( (r = fread(buf,1,(t<buf_size)?t:buf_size,f)) > 0 )
491        {
492          t -= r;
493          // cannot use send_ptp_data here
494          data->send_data(data->handle,buf,r,tmp,0,0,0);
495          tmp = 0;
496        }
497        fclose(f);
498        // XXX check that we actually read/send s bytes! (t == 0)
499
500        ptp.num_param = 1;
501        ptp.param1 = s;
502
503        free(buf);
504
505        break;
506      }
507      break;
508
509#ifdef OPT_LUA
510    // TODO this should flush data even if scripting isn't supported
511    case PTP_CHDK_ExecuteScript:
512      {
513        int s;
514        char *buf;
515
516        script_run_id++;
517        ptp.num_param = 2;
518        ptp.param1 = script_run_id;
519
520        if ( param2 != PTP_CHDK_SL_LUA )
521        {
522          ptp.code = PTP_RC_ParameterNotSupported;
523          break;
524        }
525
526        s = data->get_data_size(data->handle);
527       
528        buf = (char *) malloc(s);
529        if ( buf == NULL )
530        {
531          ptp.code = PTP_RC_GeneralError;
532          break;
533        }
534
535        recv_ptp_data(data,buf,s);
536
537        // error details will be passed in a message
538        if (script_start_ptp(buf) < 0) {
539          ptp.param2 = PTP_CHDK_S_ERRTYPE_COMPILE;
540        } else {
541          ptp.param2 = PTP_CHDK_S_ERRTYPE_NONE;
542        }
543
544        free(buf);
545       
546        break;
547      }
548    case PTP_CHDK_ReadScriptMsg:
549    {
550      char *pdata="";
551      unsigned datasize=1;
552
553      ptp_script_msg *msg = dequeue_script_msg(&msg_q_out);
554      ptp.num_param = 4;
555      if(msg) {
556        ptp.param1 = msg->type;
557        ptp.param2 = msg->subtype;
558        ptp.param3 = msg->script_id;
559        ptp.param4 = msg->size;
560        // empty messages must have a data phase, so use default if no data
561        if(msg->size) {
562            datasize = msg->size;
563            pdata = msg->data;
564        }
565          } else {
566        // return a fully formed message for easier handling
567        ptp.param1 = PTP_CHDK_S_MSGTYPE_NONE;
568        ptp.param2 = 0;
569        ptp.param3 = 0;
570        ptp.param4 = 0;
571      }
572
573      // NOTE message is lost if sending failed
574      if ( !send_ptp_data(data,pdata,datasize) )
575      {
576        ptp.code = PTP_RC_GeneralError;
577      }
578      free(msg);
579      break;
580    }
581    case PTP_CHDK_WriteScriptMsg:
582    {
583      int msg_size;
584      ptp_script_msg *msg;
585      ptp.num_param = 1;
586      ptp.param1 = PTP_CHDK_S_MSGSTATUS_OK;
587      if (!script_is_running()) {
588        ptp.param1 = PTP_CHDK_S_MSGSTATUS_NOTRUN;
589      } else if(param2 && param2 != script_run_id) {// check if target script for message is running
590        ptp.param1 = PTP_CHDK_S_MSGSTATUS_BADID;
591      } else if(script_msg_q_full(&msg_q_in)) {
592        ptp.param1 = PTP_CHDK_S_MSGSTATUS_QFULL;
593      }
594
595      msg_size = data->get_data_size(data->handle);
596
597      // if something was wrong, don't bother creating message, just flush
598      if(ptp.param1 != PTP_CHDK_S_MSGSTATUS_OK) {
599        flush_recv_ptp_data(data,msg_size);
600        break;
601      }
602      msg = ptp_script_create_msg(PTP_CHDK_S_MSGTYPE_USER,PTP_CHDK_TYPE_STRING,msg_size,NULL);
603      if ( !msg ) // malloc error or zero size
604      {
605        // if size is zero, things will get hosed no matter what
606        flush_recv_ptp_data(data,msg_size);
607        ptp.code = PTP_RC_GeneralError;
608        break;
609      }
610      msg->script_id = param2;
611      if ( !recv_ptp_data(data,msg->data,msg->size) )
612      {
613        ptp.code = PTP_RC_GeneralError;
614        free(msg);
615        break;
616      }
617      if( !enqueue_script_msg(&msg_q_in,msg) ) {
618        ptp.code = PTP_RC_GeneralError;
619        free(msg);
620      }
621      break;
622    }
623#endif
624
625    default:
626      ptp.code = PTP_RC_ParameterNotSupported;
627      break;
628  }
629
630  // send response
631  data->send_resp( data->handle, &ptp );
632 
633  return 1;
634}
635
636#endif // CAM_CHDK_PTP
Note: See TracBrowser for help on using the repository browser.