Gordons Projects

--> Projects Top-Level GIT

Merge branch 'master' of git.drogon.net:projects/wiringPi
[wiringPi] / gpio / gpio.c
1 /*
2  * gpio.c:
3  *      Set-UID command-line interface to the Raspberry Pi's GPIO
4  *      Copyright (c) 2012 Gordon Henderson
5  ***********************************************************************
6  * This file is part of wiringPi:
7  *      https://projects.drogon.net/raspberry-pi/wiringpi/
8  *
9  *    wiringPi is free software: you can redistribute it and/or modify
10  *    it under the terms of the GNU Lesser General Public License as published by
11  *    the Free Software Foundation, either version 3 of the License, or
12  *    (at your option) any later version.
13  *
14  *    wiringPi is distributed in the hope that it will be useful,
15  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *    GNU Lesser General Public License for more details.
18  *
19  *    You should have received a copy of the GNU Lesser General Public License
20  *    along with wiringPi.  If not, see <http://www.gnu.org/licenses/>.
21  ***********************************************************************
22  */
23
24 #include <wiringPi.h>
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <stdint.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <sys/types.h>
33 #include <fcntl.h>
34
35 #ifndef TRUE
36 #  define       TRUE    (1==1)
37 #  define       FALSE   (1==2)
38 #endif
39
40 #define VERSION "1.1"
41
42 static int wpMode ;
43
44 char *usage = "Usage: gpio -v\n"
45               "       gpio -h\n"
46               "       gpio [-g] <read/write/pwm/mode> ...\n"
47               "       gpio [-p] <read/write/mode> ...\n"
48               "       gpio export/edge/unexport/unexportall/exports ...\n"
49               "       gpio drive <group> <value>\n"
50               "       gpio pwm-bal/pwm-ms \n"
51               "       gpio pwmr <range> \n"
52               "       gpio load spi/i2c" ;
53
54
55 /*
56  * changeOwner:
57  *      Change the ownership of the file to the real userId of the calling
58  *      program so we can access it.
59  *********************************************************************************
60  */
61
62 static void changeOwner (char *cmd, char *file)
63 {
64   uid_t uid = getuid () ;
65   uid_t gid = getgid () ;
66
67   if (chown (file, uid, gid) != 0)
68   {
69     if (errno == ENOENT)        // Warn that it's not there
70       fprintf (stderr, "%s: Warning: File not present: %s\n", cmd, file) ;
71     else
72     {
73       fprintf (stderr, "%s: Unable to change ownership of %s: %s\n", cmd, file, strerror (errno)) ;
74       exit (1) ;
75     }
76   }
77 }
78
79
80 /*
81  * moduleLoaded:
82  *      Return true/false if the supplied module is loaded
83  *********************************************************************************
84  */
85
86 static int moduleLoaded (char *modName)
87 {
88   int len   = strlen (modName) ;
89   int found = FALSE ;
90   FILE *fd = fopen ("/proc/modules", "r") ;
91   char line [80] ;
92
93   if (fd == NULL)
94   {
95     fprintf (stderr, "gpio: Unable to check modules: %s\n", strerror (errno)) ;
96     exit (1) ;
97   }
98
99   while (fgets (line, 80, fd) != NULL)
100   {
101     if (strncmp (line, modName, len) != 0)
102       continue ;
103
104     found = TRUE ;
105     break ;
106   }
107
108   fclose (fd) ;
109
110   return found ;
111 }
112
113
114 /*
115  * doLoad:
116  *      Load either the spi or i2c modules and change device ownerships, etc.
117  *********************************************************************************
118  */
119
120 static void _doLoadUsage (char *argv [])
121 {
122   fprintf (stderr, "Usage: %s load <spi/i2c>\n", argv [0]) ;
123   exit (1) ;
124 }
125
126 static void doLoad (int argc, char *argv [])
127 {
128   char *module ;
129   char cmd [80] ;
130   char *file1, *file2 ;
131
132   if (argc != 3)
133     _doLoadUsage (argv) ;
134
135   /**/ if (strcasecmp (argv [2], "spi") == 0)
136   {
137     module = "spi_bcm2708" ;
138     file1  = "/dev/spidev0.0" ;
139     file2  = "/dev/spidev0.1" ;
140   }
141   else if (strcasecmp (argv [2], "i2c") == 0)
142   {
143     module = "i2c_bcm2708" ;
144     file1  = "/dev/i2c-0" ;
145     file2  = "/dev/i2c-1" ;
146   }
147   else
148     _doLoadUsage (argv) ;
149
150   if (!moduleLoaded (module))
151   {
152     sprintf (cmd, "modprobe %s", module) ;
153     system (cmd) ;
154   }
155
156   if (!moduleLoaded (module))
157   {
158     fprintf (stderr, "%s: Unable to load %s\n", argv [0], module) ;
159     exit (1) ;
160   }
161
162   sleep (1) ;   // To let things get settled
163
164   changeOwner (argv [0], file1) ;
165   changeOwner (argv [0], file2) ;
166 }
167
168
169
170 /*
171  * doExports:
172  *      List all GPIO exports
173  *********************************************************************************
174  */
175
176 static void doExports (int argc, char *argv [])
177 {
178   int fd ;
179   int i, l, first ;
180   char fName [128] ;
181   char buf [16] ;
182
183 // Rather crude, but who knows what others are up to...
184
185   for (first = 0, i = 0 ; i < 64 ; ++i)
186   {
187
188 // Try to read the direction
189
190     sprintf (fName, "/sys/class/gpio/gpio%d/direction", i) ;
191     if ((fd = open (fName, O_RDONLY)) == -1)
192       continue ;
193
194     if (first == 0)
195     {
196       ++first ;
197       printf ("GPIO Pins exported:\n") ;
198     }
199
200     printf ("%4d: ", i) ;
201
202     if ((l = read (fd, buf, 16)) == 0)
203       sprintf (buf, "%s", "?") ;
204  
205     buf [l] = 0 ;
206     if ((buf [strlen (buf) - 1]) == '\n')
207       buf [strlen (buf) - 1] = 0 ;
208
209     printf ("%-3s", buf) ;
210
211     close (fd) ;
212
213 // Try to Read the value
214
215     sprintf (fName, "/sys/class/gpio/gpio%d/value", i) ;
216     if ((fd = open (fName, O_RDONLY)) == -1)
217     {
218       printf ("No Value file (huh?)\n") ;
219       continue ;
220     }
221
222     if ((l = read (fd, buf, 16)) == 0)
223       sprintf (buf, "%s", "?") ;
224
225     buf [l] = 0 ;
226     if ((buf [strlen (buf) - 1]) == '\n')
227       buf [strlen (buf) - 1] = 0 ;
228
229     printf ("  %s", buf) ;
230
231 // Read any edge trigger file
232
233     sprintf (fName, "/sys/class/gpio/gpio%d/edge", i) ;
234     if ((fd = open (fName, O_RDONLY)) == -1)
235     {
236       printf ("\n") ;
237       continue ;
238     }
239
240     if ((l = read (fd, buf, 16)) == 0)
241       sprintf (buf, "%s", "?") ;
242
243     buf [l] = 0 ;
244     if ((buf [strlen (buf) - 1]) == '\n')
245       buf [strlen (buf) - 1] = 0 ;
246
247     printf ("  %-8s\n", buf) ;
248
249     close (fd) ;
250   }
251 }
252
253
254 /*
255  * doExport:
256  *      gpio export pin mode
257  *      This uses the /sys/class/gpio device interface.
258  *********************************************************************************
259  */
260
261 void doExport (int argc, char *argv [])
262 {
263   FILE *fd ;
264   int pin ;
265   char *mode ;
266   char fName [128] ;
267
268   if (argc != 4)
269   {
270     fprintf (stderr, "Usage: %s export pin mode\n", argv [0]) ;
271     exit (1) ;
272   }
273
274   pin = atoi (argv [2]) ;
275
276   mode = argv [3] ;
277
278   if ((fd = fopen ("/sys/class/gpio/export", "w")) == NULL)
279   {
280     fprintf (stderr, "%s: Unable to open GPIO export interface: %s\n", argv [0], strerror (errno)) ;
281     exit (1) ;
282   }
283
284   fprintf (fd, "%d\n", pin) ;
285   fclose (fd) ;
286
287   sprintf (fName, "/sys/class/gpio/gpio%d/direction", pin) ;
288   if ((fd = fopen (fName, "w")) == NULL)
289   {
290     fprintf (stderr, "%s: Unable to open GPIO direction interface for pin %d: %s\n", argv [0], pin, strerror (errno)) ;
291     exit (1) ;
292   }
293
294   /**/ if ((strcasecmp (mode, "in")  == 0) || (strcasecmp (mode, "input")  == 0))
295     fprintf (fd, "in\n") ;
296   else if ((strcasecmp (mode, "out") == 0) || (strcasecmp (mode, "output") == 0))
297     fprintf (fd, "out\n") ;
298   else
299   {
300     fprintf (stderr, "%s: Invalid mode: %s. Should be in or out\n", argv [1], mode) ;
301     exit (1) ;
302   }
303
304   fclose (fd) ;
305
306 // Change ownership so the current user can actually use it!
307
308   sprintf (fName, "/sys/class/gpio/gpio%d/value", pin) ;
309   changeOwner (argv [0], fName) ;
310
311   sprintf (fName, "/sys/class/gpio/gpio%d/edge", pin) ;
312   changeOwner (argv [0], fName) ;
313
314 }
315
316
317 /*
318  * doEdge:
319  *      gpio edge pin mode
320  *      Easy access to changing the edge trigger on a GPIO pin
321  *      This uses the /sys/class/gpio device interface.
322  *********************************************************************************
323  */
324
325 void doEdge (int argc, char *argv [])
326 {
327   FILE *fd ;
328   int pin ;
329   char *mode ;
330   char fName [128] ;
331
332   if (argc != 4)
333   {
334     fprintf (stderr, "Usage: %s edge pin mode\n", argv [0]) ;
335     exit (1) ;
336   }
337
338   pin  = atoi (argv [2]) ;
339   mode = argv [3] ;
340
341 // Export the pin and set direction to input
342
343   if ((fd = fopen ("/sys/class/gpio/export", "w")) == NULL)
344   {
345     fprintf (stderr, "%s: Unable to open GPIO export interface: %s\n", argv [0], strerror (errno)) ;
346     exit (1) ;
347   }
348
349   fprintf (fd, "%d\n", pin) ;
350   fclose (fd) ;
351
352   sprintf (fName, "/sys/class/gpio/gpio%d/direction", pin) ;
353   if ((fd = fopen (fName, "w")) == NULL)
354   {
355     fprintf (stderr, "%s: Unable to open GPIO direction interface for pin %d: %s\n", argv [0], pin, strerror (errno)) ;
356     exit (1) ;
357   }
358
359   fprintf (fd, "in\n") ;
360   fclose (fd) ;
361
362   sprintf (fName, "/sys/class/gpio/gpio%d/edge", pin) ;
363   if ((fd = fopen (fName, "w")) == NULL)
364   {
365     fprintf (stderr, "%s: Unable to open GPIO edge interface for pin %d: %s\n", argv [0], pin, strerror (errno)) ;
366     exit (1) ;
367   }
368
369   /**/ if (strcasecmp (mode, "none")    == 0) fprintf (fd, "none\n") ;
370   else if (strcasecmp (mode, "rising")  == 0) fprintf (fd, "rising\n") ;
371   else if (strcasecmp (mode, "falling") == 0) fprintf (fd, "falling\n") ;
372   else if (strcasecmp (mode, "both")    == 0) fprintf (fd, "both\n") ;
373   else
374   {
375     fprintf (stderr, "%s: Invalid mode: %s. Should be none, rising, falling or both\n", argv [1], mode) ;
376     exit (1) ;
377   }
378
379 // Change ownership of the value and edge files, so the current user can actually use it!
380
381   sprintf (fName, "/sys/class/gpio/gpio%d/value", pin) ;
382   changeOwner (argv [0], fName) ;
383
384   sprintf (fName, "/sys/class/gpio/gpio%d/edge", pin) ;
385   changeOwner (argv [0], fName) ;
386
387   fclose (fd) ;
388 }
389
390
391 /*
392  * doUnexport:
393  *      gpio unexport pin
394  *      This uses the /sys/class/gpio device interface.
395  *********************************************************************************
396  */
397
398 void doUnexport (int argc, char *argv [])
399 {
400   FILE *fd ;
401   int pin ;
402
403   if (argc != 3)
404   {
405     fprintf (stderr, "Usage: %s unexport pin\n", argv [0]) ;
406     exit (1) ;
407   }
408
409   pin = atoi (argv [2]) ;
410
411   if ((fd = fopen ("/sys/class/gpio/unexport", "w")) == NULL)
412   {
413     fprintf (stderr, "%s: Unable to open GPIO export interface\n", argv [0]) ;
414     exit (1) ;
415   }
416
417   fprintf (fd, "%d\n", pin) ;
418   fclose (fd) ;
419 }
420
421
422 /*
423  * doUnexportAll:
424  *      gpio unexportall
425  *      Un-Export all the GPIO pins.
426  *      This uses the /sys/class/gpio device interface.
427  *********************************************************************************
428  */
429
430 void doUnexportall (int argc, char *argv [])
431 {
432   FILE *fd ;
433   int pin ;
434
435   for (pin = 0 ; pin < 63 ; ++pin)
436   {
437     if ((fd = fopen ("/sys/class/gpio/unexport", "w")) == NULL)
438     {
439       fprintf (stderr, "%s: Unable to open GPIO export interface\n", argv [0]) ;
440       exit (1) ;
441     }
442     fprintf (fd, "%d\n", pin) ;
443     fclose (fd) ;
444   }
445 }
446
447
448 /*
449  * doMode:
450  *      gpio mode pin mode ...
451  *********************************************************************************
452  */
453
454 void doMode (int argc, char *argv [])
455 {
456   int pin ;
457   char *mode ;
458
459   if (argc != 4)
460   {
461     fprintf (stderr, "Usage: %s mode pin mode\n", argv [0]) ;
462     exit (1) ;
463   }
464
465   pin = atoi (argv [2]) ;
466
467   if ((wpMode == WPI_MODE_PINS) && ((pin < 0) || (pin >= NUM_PINS)))
468     return ;
469
470   mode = argv [3] ;
471
472   /**/ if (strcasecmp (mode, "in")   == 0) pinMode         (pin, INPUT) ;
473   else if (strcasecmp (mode, "out")  == 0) pinMode         (pin, OUTPUT) ;
474   else if (strcasecmp (mode, "pwm")  == 0) pinMode         (pin, PWM_OUTPUT) ;
475   else if (strcasecmp (mode, "up")   == 0) pullUpDnControl (pin, PUD_UP) ;
476   else if (strcasecmp (mode, "down") == 0) pullUpDnControl (pin, PUD_DOWN) ;
477   else if (strcasecmp (mode, "tri")  == 0) pullUpDnControl (pin, PUD_OFF) ;
478   else
479   {
480     fprintf (stderr, "%s: Invalid mode: %s. Should be in/out/pwm/up/down/tri\n", argv [1], mode) ;
481     exit (1) ;
482   }
483 }
484
485
486 /*
487  * doPadDrive:
488  *      gpio drive group value
489  *********************************************************************************
490  */
491
492 static void doPadDrive (int argc, char *argv [])
493 {
494   int group, val ;
495
496   if (argc != 4)
497   {
498     fprintf (stderr, "Usage: %s drive group value\n", argv [0]) ;
499     exit (1) ;
500   }
501
502   group = atoi (argv [2]) ;
503   val   = atoi (argv [3]) ;
504
505   if ((group < 0) || (group > 2))
506   {
507     fprintf (stderr, "%s: drive group not 0, 1 or 2: %d\n", argv [0], group) ;
508     exit (1) ;
509   }
510
511   if ((val < 0) || (val > 7))
512   {
513     fprintf (stderr, "%s: drive value not 0-7: %d\n", argv [0], val) ;
514     exit (1) ;
515   }
516
517   setPadDrive (group, val) ;
518 }
519
520
521 /*
522  * doWrite:
523  *      gpio write pin value
524  *********************************************************************************
525  */
526
527 static void doWrite (int argc, char *argv [])
528 {
529   int pin, val ;
530
531   if (argc != 4)
532   {
533     fprintf (stderr, "Usage: %s write pin value\n", argv [0]) ;
534     exit (1) ;
535   }
536
537   pin = atoi (argv [2]) ;
538
539   if ((wpMode == WPI_MODE_PINS) && ((pin < 0) || (pin >= NUM_PINS)))
540     return ;
541
542   val = atoi (argv [3]) ;
543
544   /**/ if (val == 0)
545     digitalWrite (pin, LOW) ;
546   else
547     digitalWrite (pin, HIGH) ;
548 }
549
550
551 /*
552  * doRead:
553  *      Read a pin and return the value
554  *********************************************************************************
555  */
556
557 void doRead (int argc, char *argv []) 
558 {
559   int pin, val ;
560
561   if (argc != 3)
562   {
563     fprintf (stderr, "Usage: %s read pin\n", argv [0]) ;
564     exit (1) ;
565   }
566
567   pin = atoi (argv [2]) ;
568
569   if ((wpMode == WPI_MODE_PINS) && ((pin < 0) || (pin >= NUM_PINS)))
570   {
571     printf ("0\n") ;
572     return ;
573   }
574
575   val = digitalRead (pin) ;
576
577   printf ("%s\n", val == 0 ? "0" : "1") ;
578 }
579
580
581 /*
582  * doPwm:
583  *      Output a PWM value on a pin
584  *********************************************************************************
585  */
586
587 void doPwm (int argc, char *argv [])
588 {
589   int pin, val ;
590
591   if (argc != 4)
592   {
593     fprintf (stderr, "Usage: %s pwm <pin> <value>\n", argv [0]) ;
594     exit (1) ;
595   }
596
597   pin = atoi (argv [2]) ;
598
599   if ((wpMode == WPI_MODE_PINS) && ((pin < 0) || (pin >= NUM_PINS)))
600     return ;
601
602   val = atoi (argv [3]) ;
603
604   pwmWrite (pin, val) ;
605 }
606
607
608 /*
609  * doPwmMode: doPwmRange:
610  *      Change the PWM mode and Range values
611  *********************************************************************************
612  */
613
614 static void doPwmMode (int mode)
615 {
616   pwmSetMode (mode) ;
617 }
618
619 static void doPwmRange (int argc, char *argv [])
620 {
621   unsigned int range ;
622
623   if (argc != 3)
624   {
625     fprintf (stderr, "Usage: %s pwmr <range>\n", argv [0]) ;
626     exit (1) ;
627   }
628
629   range = (unsigned int)strtoul (argv [2], NULL, 10) ;
630
631   if (range == 0)
632   {
633     fprintf (stderr, "%s: range must be > 0\n", argv [0]) ;
634     exit (1) ;
635   }
636
637   pwmSetRange (range) ;
638 }
639
640
641 /*
642  * main:
643  *      Start here
644  *********************************************************************************
645  */
646
647 int main (int argc, char *argv [])
648 {
649   int i ;
650
651   if (argc == 1)
652   {
653     fprintf (stderr, "%s: %s\n", argv [0], usage) ;
654     return 1 ;
655   }
656
657   if (strcasecmp (argv [1], "-h") == 0)
658   {
659     printf ("%s: %s\n", argv [0], usage) ;
660     return 0 ;
661   }
662
663   if (strcasecmp (argv [1], "-v") == 0)
664   {
665     printf ("gpio version: %s\n", VERSION) ;
666     printf ("Copyright (c) 2012 Gordon Henderson\n") ;
667     printf ("This is free software with ABSOLUTELY NO WARRANTY.\n") ;
668     printf ("For details type: %s -warranty\n", argv [0]) ;
669     return 0 ;
670   }
671
672   if (strcasecmp (argv [1], "-warranty") == 0)
673   {
674     printf ("gpio version: %s\n", VERSION) ;
675     printf ("Copyright (c) 2012 Gordon Henderson\n") ;
676     printf ("\n") ;
677     printf ("    This program is free software; you can redistribute it and/or modify\n") ;
678     printf ("    it under the terms of the GNU Leser General Public License as published\n") ;
679     printf ("    by the Free Software Foundation, either version 3 of the License, or\n") ;
680     printf ("    (at your option) any later version.\n") ;
681     printf ("\n") ;
682     printf ("    This program is distributed in the hope that it will be useful,\n") ;
683     printf ("    but WITHOUT ANY WARRANTY; without even the implied warranty of\n") ;
684     printf ("    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n") ;
685     printf ("    GNU Lesser General Public License for more details.\n") ;
686     printf ("\n") ;
687     printf ("    You should have received a copy of the GNU Lesser General Public License\n") ;
688     printf ("    along with this program. If not, see <http://www.gnu.org/licenses/>.\n") ;
689     printf ("\n") ;
690     return 0 ;
691   }
692
693   if (geteuid () != 0)
694   {
695     fprintf (stderr, "%s: Must be root to run. Program should be suid root. This is an error.\n", argv [0]) ;
696     return 1 ;
697   }
698
699 // Initial test for /sys/class/gpio operations:
700
701   /**/ if (strcasecmp (argv [1], "exports"    ) == 0)   { doExports     (argc, argv) ;  return 0 ; }
702   else if (strcasecmp (argv [1], "export"     ) == 0)   { doExport      (argc, argv) ;  return 0 ; }
703   else if (strcasecmp (argv [1], "edge"       ) == 0)   { doEdge        (argc, argv) ;  return 0 ; }
704   else if (strcasecmp (argv [1], "unexportall") == 0)   { doUnexportall (argc, argv) ;  return 0 ; }
705   else if (strcasecmp (argv [1], "unexport"   ) == 0)   { doUnexport    (argc, argv) ;  return 0 ; }
706
707 // Check for drive or load commands:
708
709   if (strcasecmp (argv [1], "drive") == 0)      { doPadDrive (argc, argv) ; return 0 ; }
710   if (strcasecmp (argv [1], "load" ) == 0)      { doLoad     (argc, argv) ; return 0 ; }
711
712 // Check for -g argument
713
714   if (strcasecmp (argv [1], "-g") == 0)
715   {
716     if (wiringPiSetupGpio () == -1)
717     {
718       fprintf (stderr, "%s: Unable to initialise GPIO mode.\n", argv [0]) ;
719       exit (1) ;
720     }
721
722     for (i = 2 ; i < argc ; ++i)
723       argv [i - 1] = argv [i] ;
724     --argc ;
725     wpMode = WPI_MODE_GPIO ;
726   }
727
728 // Check for -p argument for PiFace
729
730   else if (strcasecmp (argv [1], "-p") == 0)
731   {
732     if (wiringPiSetupPiFaceForGpioProg () == -1)
733     {
734       fprintf (stderr, "%s: Unable to initialise PiFace.\n", argv [0]) ;
735       exit (1) ;
736     }
737
738     for (i = 2 ; i < argc ; ++i)
739       argv [i - 1] = argv [i] ;
740     --argc ;
741     wpMode = WPI_MODE_PIFACE ;
742   }
743
744 // Default to wiringPi mode
745
746   else
747   {
748     if (wiringPiSetup () == -1)
749     {
750       fprintf (stderr, "%s: Unable to initialise wiringPi mode\n", argv [0]) ;
751       exit (1) ;
752     }
753     wpMode = WPI_MODE_PINS ;
754   }
755
756 // Check for PWM operations
757
758   if (wpMode != WPI_MODE_PIFACE)
759   {
760     if (strcasecmp (argv [1], "pwm-bal") == 0)  { doPwmMode  (PWM_MODE_BAL) ; return 0 ; }
761     if (strcasecmp (argv [1], "pwm-ms")  == 0)  { doPwmMode  (PWM_MODE_MS) ;  return 0 ; }
762     if (strcasecmp (argv [1], "pwmr")    == 0)  { doPwmRange (argc, argv) ;   return 0 ; }
763   }
764
765 // Check for wiring commands
766
767   /**/ if (strcasecmp (argv [1], "read" ) == 0) doRead     (argc, argv) ;
768   else if (strcasecmp (argv [1], "write") == 0) doWrite    (argc, argv) ;
769   else if (strcasecmp (argv [1], "pwm"  ) == 0) doPwm      (argc, argv) ;
770   else if (strcasecmp (argv [1], "mode" ) == 0) doMode     (argc, argv) ;
771   else
772   {
773     fprintf (stderr, "%s: Unknown command: %s.\n", argv [0], argv [1]) ;
774     exit (1) ;
775   }
776   return 0 ;
777 }