1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import os
19 import re
20 import time
21 from . import led_driver
22 import copy
23 import subprocess
24
25
26 BITS_PER_PIXEL = 4
27 DIM_OF_MATRIX = 8
28 initialized = False
29 spi_initialized = False
30 width = 0
31 height = 0
32 container_math_coords = True
36 """Checks that init_matrices has been called and throws an error if not
37 @raise RuntimeError: If matrices have not been initialized."""
38 global initialized
39 if not initialized:
40 raise RuntimeError("Matrices must be initialized first.")
41
44 """Checks if given color is number between 0-16 if an int or 0-f, or - if string
45 @param color: A color to check that is valid
46 @type color: string or int
47 """
48 if type(color) == int:
49 if color < 0x0 or color > 0x10:
50 return False
51 return True
52 elif type(color) == str:
53 if not re.match(r'^[A-Za-z0-9-]$', color):
54 return False
55 return True
56 return False
57
60 """Converts the given color to an int.
61 @param color: A color to be converted
62 @type color: string or int
63
64 @raise ValueError: Fails L{_valid_color} check
65
66 @return: Either the same int back again if color was an int or the int of a converted string
67 @rtype: int
68 """
69 if not _valid_color(color):
70 raise ValueError("Invalid Color: must be a string between 0-f or '-'" +
71 " or a number 0-16 with 16 being transparent")
72 if type(color) == int:
73 return color
74 elif type(color) == str:
75 if color == '-':
76 return 0x10
77 return int(color, 16)
78 raise RuntimeError("Invalid color")
79
82 """Converts given math coordinates to standard programming coordinates
83 @param x: x coordinate in math coordinates
84 @param y: y coordinate in math coordinates
85 @rtype: tuple
86 @return: (x,y) coordinates in standard programming coordinates"""
87 return x, (height - 1 - y)
88
93
94
95 -def init_matrices(mat_list=[(0, 0, 0)], math_coords=True, spi_speed=125000, spi_port=0):
96 """Creates a chain of led matrices set at particular offsets into the frame buffer
97 The order of the led matrices in the list indicate the order they are
98 physically hooked up with the first one connected to Pi.
99
100 @param mat_list: list of tuples that contains led matrix and offset
101 (in math coordinates if math_coords==True, else in programmer coordinate if math_coords==False)
102 ex: [(0,0,0),(7,0,90)]
103 @type mat_list: list of size 2 or 3 tuples (mix or match)
104 @param math_coords: True to use math coordinates, False to use programming coordinates
105 @type math_coords: Boolean
106 """
107 global initialized
108 global width
109 global height
110 global container_math_coords
111 if initialized:
112 cleanup()
113
114 width = max([matrix[0] for matrix in mat_list]) + DIM_OF_MATRIX
115 height = max([matrix[1] for matrix in mat_list]) + DIM_OF_MATRIX
116 container_math_coords = math_coords
117 if container_math_coords:
118 for i in range(len(mat_list)):
119
120
121 if len(mat_list[i]) > 2:
122 mat_list[i] = (mat_list[i][0], (height-1 - mat_list[i][1]) - (DIM_OF_MATRIX-1), mat_list[i][2])
123 else:
124 mat_list[i] = (mat_list[i][0], (height-1 - mat_list[i][1]) - (DIM_OF_MATRIX-1))
125 led_driver.init_matrices(mat_list, len(mat_list), width, height)
126
127
128 global spi_initialized
129 if not spi_initialized:
130 led_driver.init_SPI(spi_speed, spi_port)
131 spi_initialized = True
132
133 initialized = True
134
135
136
137 -def init_grid(num_rows=None, num_cols=None, angle=0, math_coords=True, spi_speed=125000, spi_port=0):
138 """Initiallizes led matrices in a grid pattern with either a given number
139 of rows and columns.
140 If num_rows and num_cols is not given, it will detect the number of matrices you have
141 and automatically set up a grid pattern with a max number of columns of 4.
142
143 @param num_rows: Number of rows the grid is in (when angle == 0)
144 @param num_cols: Number of columns the gird is in (when angle == 0)
145 @param math_coords: True to use math coordinates, False to use programming coordinates
146 @type math_coords: Boolean
147 @param angle: Angle to rotate the coordinate system once initialized. (must be 90 degree multiple)
148 @type angle: int
149
150 @raise ValueError: num_rows*num_cols != number of matrices
151 @raise ValueError: angle is not a multiple of 90
152 """
153
154
155
156 global spi_initialized
157 if not spi_initialized:
158 led_driver.init_SPI(spi_speed, spi_port)
159 spi_initialized = True
160
161
162 if num_cols is None:
163
164
165 num_matrices = led_driver.detect()
166 if num_rows is None:
167
168 for cols in reversed(range(5)):
169 rows = float(num_matrices)/cols
170 if rows.is_integer():
171 num_rows = int(rows)
172 num_cols = cols
173 break
174 else:
175 num_cols = int(num_matrices/num_rows)
176
177 if num_cols*num_rows != num_matrices:
178 raise ValueError("Invalid number of rows and columns")
179
180 elif num_rows is None:
181 raise ValueError("If you are providing num_cols you must also provide num_rows.")
182
183
184 if angle % 90 != 0:
185 raise ValueError("Angle must be a multiple of 90.")
186 angle = angle % 360
187
188 mat_list = []
189 if angle == 0:
190 for row in range(num_rows):
191 if row % 2 == 1:
192 for column in range(num_cols-1,-1,-1):
193 mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 180))
194 else:
195 for column in range(num_cols):
196 mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 0))
197 elif angle == 90:
198 for row in range(num_rows):
199 if row % 2 == 1:
200 for column in range(num_cols-1,-1,-1):
201 mat_list.append(((num_rows-row - 1)*DIM_OF_MATRIX, column*DIM_OF_MATRIX, 270))
202 else:
203 for column in range(num_cols):
204 mat_list.append(((num_rows-row - 1)*DIM_OF_MATRIX, column*DIM_OF_MATRIX, 90))
205 elif angle == 180:
206 for row in range(num_rows-1,-1,-1):
207 if row % 2 == 0:
208 for column in range(num_cols-1,-1,-1):
209 mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 180))
210 else:
211 for column in range(num_cols):
212 mat_list.append((column*DIM_OF_MATRIX, row*DIM_OF_MATRIX, 0))
213 elif angle == 270:
214 for row in range(num_rows):
215 if row % 2 == 1:
216 for column in range(num_cols-1,-1,-1):
217 mat_list.append((row*DIM_OF_MATRIX, (num_cols - 1- column)*DIM_OF_MATRIX, 90))
218 else:
219 for column in range(num_cols):
220 mat_list.append((row*DIM_OF_MATRIX, (num_cols - 1 - column)*DIM_OF_MATRIX, 270))
221
222 global container_math_coords
223 init_matrices(mat_list, math_coords=False, spi_speed=spi_speed, spi_port=spi_port)
224 container_math_coords = math_coords
225
227 """Shows the current framebuffer on the display.
228 Tells the led_driver to send framebuffer to SPI port.
229 Refreshes the display using current framebuffer.
230
231 @rtype: int
232 @returns: 1 on success
233 """
234 _init_check()
235 return led_driver.flush()
236
250
251 -def fill(color=0xF):
252 """Fills the framebuffer with the given color.
253 @param color: Color to fill the entire display with
254 @type color: int or string (0-F or 16 or '-' for transparent)
255 """
256 _init_check()
257 led_driver.fill(_convert_color(color))
258
261 """Clears display"""
262 fill(0)
263
264 -def point(x, y=None, color=0xF):
265 """Sends point to the framebuffer.
266 @note: Will not display the point until show() is called.
267
268 @param x, y: Coordinates to place point
269 @type x, y: (int, int)
270 @param color: Color to display at point
271 @type color: int or string (0-F or 16 or '-' for transparent)
272
273 @rtype: int
274 @returns: 1 on success
275 """
276 _init_check()
277 color = _convert_color(color)
278 if color < 16:
279 global width
280 global height
281
282 if y is None and type(x) is tuple:
283 x, y = x
284 if x < 0 or x >= width or y < 0 or y >= height:
285 return
286 if container_math_coords:
287 x, y = _convert_to_std_coords(x, y)
288 return led_driver.point(int(x), int(y), color)
289
290 -def rect(origin, dimensions, fill=True, color=0xF):
291 """Creates a rectangle from start point using given dimensions
292
293 @param origin: The bottom left corner of rectange (if math_coords == True).
294 The top left corner of rectangle (if math_coords == False)
295 @type origin: (x,y) tuple
296 @param dimensions: width and height of rectangle
297 @type dimensions: (width, height) tuple
298 @param fill: Whether to fill the rectangle or make it hollow
299 @type fill: boolean
300 @param color: Color to display at point
301 @type color: int or string (0-F or 16 or '-' for transparent)
302 """
303 x, y = origin
304 width, height = dimensions
305
306 if fill:
307 for x_offset in range(width):
308 line((x + x_offset, y), (x + x_offset, y + height - 1), color)
309 else:
310 line((x, y), (x, y + height - 1), color)
311 line((x, y + height - 1), (x + width - 1, y + height - 1), color)
312 line((x + width - 1, y + height - 1), (x + width - 1, y), color)
313 line((x + width - 1, y), (x, y), color)
314
318 return 1 if n >= 0 else -1
319
320 -def line(point_a, point_b, color=0xF):
321 """Create a line from point_a to point_b.
322 Uses Bresenham's Line Algorithm U{http://en.wikipedia.org/wiki/Bresenham's_line_algorithm}
323 @type point_a, point_b: (x,y) tuple
324 @param color: Color to display at point
325 @type color: int or string (0-F or 16 or '-' for transparent)
326 """
327 x1, y1 = point_a
328 x2, y2 = point_b
329 dx = abs(x2 - x1)
330 dy = abs(y2 - y1)
331 sx = 1 if x1 < x2 else -1
332 sy = 1 if y1 < y2 else -1
333 err = dx - dy
334 while True:
335 point(x1, y1, color)
336 if (x1 == x2 and y1 == y2) or x1 >= width or y1 >= height:
337 break
338 e2 = 2*err
339 if e2 > -dy:
340 err -= dy
341 x1 += sx
342 if e2 < dx:
343 err += dx
344 y1 += sy
345
352
353
354 -def text(text, origin=(0, 0), crop_origin=(0, 0), crop_dimensions=None, font_name="small", font_path=None):
355 """Sets given string to be displayed on the led matrix
356
357 Example:
358 >>> text("Hello World", (0,0), (0,1), (0,5))
359 >>> show()
360 Displays only part of the first vertical line in 'H'
361
362 @param origin, crop_origin, crop_dimensions: See L{sprite}
363
364 @param text: text to display
365 @param text: string or L{LEDText}
366 @param font_name: Font folder (or .font) file to use as font face
367 @type font_name: string
368 @param font_path: Directory to use to look for fonts. If None it will used default directory in dist-packages
369 @type font_path: string
370
371 @returns: LEDText sprite object used to create text
372 @rtype: L{LEDText}
373 """
374 if type(text) == str:
375 text = LEDText(text, font_name=font_name, font_path=font_path)
376 elif type(text) != LEDText and type(text) != LEDSprite:
377 raise ValueError("Invalid text")
378 sprite(text, origin, crop_origin, crop_dimensions)
379 return text
380
381
382 -def sprite(sprite, origin=(0,0), crop_origin=(0,0), crop_dimensions=None):
383 """Sets given sprite into the framebuffer.
384
385 @param origin: Bottom left position to diplay text (top left if math_coords == False)
386 @type origin: (x,y) tuple
387 @param crop_origin: Position to crop into the sprite relative to the origin
388 @type crop_origin: (x,y) tuple
389 @param crop_dimensions: x and y distance from the crop_origin to display
390 - Keep at None to not crop and display sprite all the way to top right corner (bottom right for math_coords == False)
391 @type crop_dimensions: (x,y) tuple
392 """
393 if type(sprite) == str:
394 sprite = LEDSprite(sprite)
395 elif type(sprite) != LEDText and type(sprite) != LEDSprite:
396 raise ValueError("Invalid sprite")
397
398 _init_check()
399 global width
400 global height
401
402 x_pos, y_pos = origin
403 x_crop, y_crop = crop_origin
404
405
406 if x_crop < 0 or y_crop < 0:
407 raise ValueError("crop_origin must be positive numbers")
408
409
410 if crop_dimensions is None:
411 x_crop_dim, y_crop_dim = (sprite.width, sprite.height)
412 else:
413 x_crop_dim, y_crop_dim = crop_dimensions
414 if x_crop_dim < 0 or y_crop_dim < 0:
415 raise ValueError("crop_dimensions must be positive numbers")
416
417
418 x_start = x_pos + x_crop
419 y_start = y_pos + y_crop
420
421 if x_start >= width or y_start >= height:
422 return
423
424
425 x_end = min(x_pos + x_crop + x_crop_dim, width, x_pos + sprite.width)
426 y_end = min(y_pos + y_crop + y_crop_dim, height, y_pos + sprite.height)
427
428
429 y = max(y_start,0)
430 while y < y_end:
431 x = max(x_start, 0)
432 while x < x_end:
433 x_sprite = x - x_start + x_crop
434 y_sprite = y - y_start + y_crop
435 x_sprite = int(x_sprite)
436 y_sprite = int(y_sprite)
437 if container_math_coords:
438 y_sprite = sprite.height - 1 - y_sprite
439 point((x, y), color=sprite.bitmap[y_sprite][x_sprite])
440 x += 1
441 y += 1
442
444 """Sends the entire frame (represented as a numpy bitmap) to the led matrix.
445
446 @param numpy_bitmap: A ndarray representing the entire framebuffer to display.
447 @type numpy_bitmap: numpy ndarray
448 @note: bitmap dimensions must be the same as the dimensions of the container (non rotated).
449 """
450 led_driver.frame(numpy_bitmap)
451
454 """Allows the creation of a LED Sprite that is defined in a text file.
455 @note: The text file must only contain hex numbers 0-9, a-f, A-F, or - (dash)
456 @note: The hex number indicates pixel color and "-" indicates a transparent pixel
457 """
458 - def __init__(self, filename=None, height=0, width=0, color=0x0):
459 """Creates a L{LEDSprite} object from the given .spr file or image file or creates an empty sprite of given
460 height and width if filename == None.
461
462 @param filename: The full path location of a .spr sprite file or image file
463 @type: string
464 @param height: The height of given sprite if creating an empty sprite or want to resize a sprite from and image file.
465 @type height: int
466 @param width: The width of given sprite if creating an empty sprite or want to resize a sprite from and image file.
467 @type width: int
468 @param color: Color to display at point
469 @type color: int or string (0-F or 16 or '-' for transparent)
470 """
471 bitmap = []
472 bitmap_width = 0
473 bitmap_height = 0
474 if filename is not None:
475 filename = filename.strip()
476 self.filename = filename
477
478 proc = subprocess.Popen("file " + str(filename), shell=True, stdout=subprocess.PIPE)
479 output, errors = proc.communicate()
480 if type(output) == bytes:
481 output = output.decode()
482 if errors is not None or (output.find("ERROR") != -1):
483 raise IOError(output)
484
485 if output.find("text") != -1 or output.find("FORTRAN") != -1:
486 if filename[-4:] != ".spr":
487 raise ValueError("Filename must have '.spr' extension.")
488 f = open(filename, 'r')
489 for line in f:
490 leds = [_convert_color(char) for char in line.split()]
491
492 if bitmap_width != 0:
493 if len(leds) != bitmap_width:
494 raise ValueError("Sprite has different widths")
495 else:
496 bitmap_width = len(leds)
497 bitmap.append(leds)
498 bitmap_height += 1
499 f.close()
500
501 elif output.find("image") != -1:
502 import scipy.misc, numpy, sys
503 if sys.version_info[0] > 2:
504 raise ValueError("As of now, only python 2 supports images to sprites.")
505
506
507 if height <= 0 or width <= 0:
508 from PIL import Image
509 im = Image.open(filename)
510 im_width, im_height = im.size
511 bitmap_height = min(height, im_height)
512 bitmap_width = min(width, bitmap_height * (im_width / im_height))
513 else:
514 bitmap_height = height
515 bitmap_width = width
516
517 image = scipy.misc.imread(filename, flatten=True)
518 con_image = scipy.misc.imresize(image, (bitmap_width, bitmap_height), interp='cubic')
519 con_image = numpy.transpose(con_image)
520 con_image = numpy.fliplr(con_image)
521 con_image = numpy.rot90(con_image, k=1)
522 bitmap = [[int(pixel*16/255) for pixel in line] for line in con_image]
523 else:
524 raise IOError("Unsupported filetype")
525 else:
526
527 bitmap = [[color for i in range(width)] for j in range(height)]
528 bitmap_height = height
529 bitmap_width = width
530
531 self.bitmap = bitmap
532
533
534
535 @property
537 return len(self.bitmap[0])
538
539 @property
541 return len(self.bitmap)
542
544 """Appends given sprite to the right of itself.
545
546 @param sprite: sprite to append
547 @type sprite: L{LEDSprite}
548 @note: height of given sprite must be <= to itself otherwise will be truncated
549 """
550 for i, line in enumerate(self.bitmap):
551 if i >= sprite.height:
552
553 tran_line = [0x10 for j in range(sprite.width)]
554 self.bitmap[i] = sum([line, tran_line], [])
555 else:
556 self.bitmap[i] = sum([line, sprite.bitmap[i]], [])
557
558
559
561 """Sets given color to given x and y coordinate in sprite
562
563 @param point: point relative to sprite to set point
564 @type point: (x,y)
565 @param color: Color to display at point
566 @type color: int or string (0-F or 16 or '-' for transparent)
567
568 @return: None if coordinate is not valid
569 """
570 x, y = point
571 if x >= self.width or y >= self.height or x < 0 or y < 0:
572 return None
573 self.bitmap[y][x] = _convert_color(color)
574
576 """
577 @rtype: int
578 @returns: int of color at given origin or None
579 """
580 if x >= self.width or y >= self.height or x < 0 or y < 0:
581 return None
582 return self.bitmap[y][x]
583
584
586 """Saves sprite bitmap to given .spr file.
587
588 @param filename: relative filename path
589 @type filename: string
590 @note: It will truncate filename if it already exists.
591 """
592 filename = filename.strip()
593 if filename[-4:] != ".spr":
594 raise ValueError("Filename must have '.spr' extension.")
595 f = open(filename, 'w')
596 for line in self.bitmap:
597 for pixel in line:
598 if pixel > 15:
599 f.write("- ")
600 else:
601 f.write(hex(pixel)[2] + " ")
602 f.write("\n")
603 f.close()
604
606 """Rotates sprite at 90 degree intervals.
607
608 @returns: self
609 @rtype: L{LEDSprite}
610
611 @param angle: angle to rotate self in an interval of 90 degrees
612 @type angle: int
613
614 @returns: self
615 @rtype: L{LEDSprite}
616 @raises ValueError: If angle is not multiple of 90
617 @note: If no angle given, will rotate sprite 90 degrees.
618 """
619 if angle % 90 != 0:
620 raise ValueError("Angle must be a multiple of 90.")
621
622 angle = angle % 360
623 if angle == 90:
624 bitmap = []
625 for i in range(self.width):
626 bitmap.append([row[i] for row in reversed(self.bitmap)])
627 self.bitmap = bitmap
628
629
630
631
632
633 elif angle == 180:
634 self.bitmap.reverse()
635 for row in self.bitmap:
636 row.reverse()
637
638 elif angle == 270:
639 bitmap = []
640 for i in range(self.width-1,-1,-1):
641 bitmap.append([row[i] for row in self.bitmap])
642 self.bitmap = bitmap
643
644
645
646
647 return self
648
650 """Same as L{rotate} only it returns a copy of the rotated sprite
651 and does not affect the original.
652 @returns: Rotated sprite
653 @rtype: L{LEDSprite}
654 """
655 sprite_copy = copy.deepcopy(self)
656 sprite_copy.rotate(angle)
657 return sprite_copy
658
660 """Copies sprite
661 @returns: A copy of sprite without affecting original sprite
662 @rtype: L{LEDSprite}
663 """
664 return copy.deepcopy(self)
665
667 """Inverts the sprite.
668 @returns: self
669 @rtype: L{LEDSprite}
670 """
671 for y, line in enumerate(self.bitmap):
672 for x, pixel in enumerate(line):
673 if pixel < 16:
674 self.bitmap[y][x] = 15 - pixel
675
677 """Same as L{invert} only it returns a copy of the inverted sprite
678 and does not affect the original.
679 @returns: Inverted sprite
680 @rtype: L{LEDSprite}
681 """
682 sprite_copy = copy.deepcopy(self)
683 sprite_copy.invert()
684 return sprite_copy
685
687 """Flips the sprite horizontally.
688 @returns: self
689 @rtype: L{LEDSprite}
690 """
691 self.bitmap.reverse()
692 return self
693
695 """Same as L{flip_horizontal} only it returns a copy of the flipped sprite
696 and does not affect the original.
697 @returns: sprite flipped horizontally
698 @rtype: L{LEDSprite}
699 """
700 sprite_copy = copy.deepcopy(self)
701 sprite_copy.flip_horizontal()
702 return sprite_copy
703
704
706 """Flips the sprite vertically.
707 @returns: self
708 @rtype: L{LEDSprite}
709 """
710 for line in self.bitmap:
711 line.reverse()
712 return self
713
715 """Same as L{flip_vertical} only it returns a copy of the flipped sprite
716 and does not affect the original.
717 @returns: sprite flipped vertically
718 @rtype: L{LEDSprite}
719 """
720 sprite_copy = copy.deepcopy(self)
721 sprite_copy.flip_vertical()
722 return sprite_copy
723
726 """Converts given character to a sprite.
727
728 @param char: character to convert (must be of length == 1)
729 @type char: string
730 @param font_path: Relative location of font face to use.
731 @type font_path: string
732 @rtype: L{LEDSprite}
733 """
734 if not (type(char) == str and len(char) == 1):
735 raise ValueError("Not a character")
736 orig_font_path = font_path
737 if char.isdigit():
738 font_path = os.path.join(font_path, "numbers", char + ".spr")
739 elif char.isupper():
740 font_path = os.path.join(font_path, "upper", char + ".spr")
741 elif char.islower():
742 font_path = os.path.join(font_path, "lower", char + ".spr")
743 elif char.isspace():
744 font_path = os.path.join(font_path, "space.spr")
745 else:
746 font_path = os.path.join(font_path, "misc", str(ord(char)) + ".spr")
747
748 if os.path.isfile(font_path):
749 return LEDSprite(font_path)
750 else:
751 return LEDSprite(os.path.join(orig_font_path, "unknown.spr"))
752
753
754
755 -class LEDText(LEDSprite):
756 """A L{LEDSprite} object of a piece of text."""
757 - def __init__(self, message, char_spacing=1, font_name="small", font_path=None):
758 """Creates a text sprite of the given string
759 This object can be used the same way a sprite is useds
760
761 @param char_spacing: number pixels between characters
762 @type char_spacing: int
763 @param font_name: Font folder (or .font) file to use as font face
764 @type font_name: string
765 @param font_path: Directory to use to look for fonts. If None it will used default directory in dist-packages
766 @type font_path: string
767 """
768 if font_path is None:
769 this_dir, this_filename = os.path.split(__file__)
770
771 font_path = os.path.join(this_dir, "font")
772
773 if not os.path.isdir(font_path):
774 raise IOError("Font path does not exist.")
775 orig_font_path = font_path
776
777
778 font_path = os.path.join(font_path, font_name)
779
780
781 if not os.path.isdir(font_path):
782 f = open(os.path.join(orig_font_path, font_name + ".font"), 'r')
783 font_path = os.path.join(orig_font_path, f.read().strip())
784 f.close()
785
786 message = message.strip()
787 if len(message) == 0:
788 super(LEDSprite, self).__init__()
789 return
790
791
792 init_sprite = _char_to_sprite(message[0], font_path)
793
794
795 if len(message) > 1:
796
797 for char in message[1:]:
798
799 init_sprite.append(LEDSprite(height=init_sprite.height, width=char_spacing, color=0x10))
800
801 sprite = _char_to_sprite(char, font_path)
802 if sprite.height != init_sprite.height:
803 raise ValueError("Height of character sprites must all be the same.")
804
805 init_sprite.append(sprite)
806
807 self.bitmap = init_sprite.bitmap
808
809
810
811 -def _main():
812 init_matrices()
813 while 1:
814 for x in range(8):
815 for y in range(8):
816 point(x, y)
817 show()
818 time.sleep(0.5);
819 erase()
820
821 if __name__ == "__main__":
822 _main()
823