├── .gitignore
├── .project
├── .pydevproject
├── .settings
└── org.eclipse.core.resources.prefs
├── LICENSE
├── README.md
├── bin
└── .gitignore
├── doc
├── screenshoot1.png
├── simbolbrowser.png
├── snipplets.png
├── terminal.png
├── toolbar.png
└── toolbar.svg
├── freeze_setup.py
├── genexe.bat
├── mk_install.nsi
├── requeriments.txt
├── share
└── uPyIDE
│ ├── fakelibs
│ └── pyb.py
│ ├── help.css
│ ├── help.html
│ ├── help.md
│ ├── images
│ ├── about.png
│ ├── document-new.png
│ ├── document-open.png
│ ├── document-save.png
│ ├── download.png
│ ├── run.png
│ ├── splash.png
│ ├── terminal-out.png
│ ├── terminal.png
│ ├── uPyIDE.ico
│ ├── uPyIDE.png
│ └── upload.png
│ └── snipplet
│ ├── Main_ExtInt.py
│ ├── Main_PWM.py
│ ├── Main_Timers.py
│ ├── Main_Timers_2.py
│ ├── Main_dac.py
│ ├── Main_gc.py
│ ├── Main_gpios.py
│ ├── Main_perf.py
│ ├── Main_perf2.py
│ ├── Main_python.py
│ ├── Main_rgb.py
│ ├── Main_uart.py
│ ├── Main_welcome.py
│ ├── snipplet_class.py
│ ├── snipplet_iterator.py
│ ├── snipplet_main.py
│ ├── snipplet_open.py
│ ├── snipplet_try.py
│ └── snipplets.xml
├── src
├── images
│ ├── about.svg
│ ├── document-new.svg
│ ├── document-open.svg
│ ├── document-save.svg
│ ├── download.svg
│ ├── run.svg
│ ├── terminal-out.svg
│ ├── terminal.svg
│ └── upload.svg
├── myDef.py
├── pyqode_i18n.py
├── server.py
├── termWidget.py
└── uPyIDE.py
└── svg2png.bat
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 |
55 | # Sphinx documentation
56 | docs/_build/
57 |
58 | # PyBuilder
59 | target/
60 |
61 | *.exe
62 | /*.nja
63 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | uPyEDU-CIAA
4 |
5 |
6 |
7 |
8 |
9 | org.python.pydev.PyDevBuilder
10 |
11 |
12 |
13 |
14 |
15 | org.python.pydev.pythonNature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.pydevproject:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | /${PROJECT_DIR_NAME}/src
5 |
6 | python 3.0
7 | Default
8 |
9 | /usr/local/lib/python3.4/dist-packages/pyqode/qt
10 | /usr/local/lib/python3.4/dist-packages/pyqode/core
11 | /usr/local/lib/python3.4/dist-packages/pyqode/python
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.core.resources.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | encoding//src/resources.py=utf-8
3 | encoding//src/server.py=utf-8
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # uPython IDE
2 |
3 | This is the IDE for micropython on EDU-CIAA.
4 |
5 | #### Requeriments
6 |
7 | - Python 3.X
8 | - PyQt or PySide
9 | - PyQode
10 | - PySerial
11 | - PyTE
12 | - tendo
13 |
14 | #### Requeriment instalation
15 |
16 | On ubuntu and derivates, this require python3, cmake, qt4-qmake, libqt4-core
17 | and libqt4-gui installed into the system. You can install all with:
18 |
19 | ```bash
20 | sudo apt-get install python3 cmake qt4-qmake libqt4-core libqt4-gui
21 | ```
22 |
23 | ```bash
24 | pip install pyside pyqode.python pyserial pyte tendo
25 | ```
26 |
27 | Replace pip by pip3 or command of your distribution.
28 |
29 | In Ubuntu, PySide can fail to compile. At this case you can use PyQt4 and
30 | others packages from official repositories
31 |
32 | ```bash
33 | sudo apt-get install python3 python3-qt4
34 | sudo pip3 install pyserial tendo pyte pyqode.python markdown
35 | ```
36 |
37 | ### Characteristics
38 |
39 | - Syntax higlight
40 | - Autocomplete
41 | - Online help
42 | - Parameter lints
43 | - PEP8 code style lints
44 | - Simbol browser
45 | - Code Snipplets
46 | - Embedded serial terminal
47 |
48 | ### Usage
49 |
50 | This is the main window
51 | 
52 |
53 | #### Tool Bar
54 | 
55 |
56 | Actions:
57 | - **New**: Clear context of editor
58 | - **Open**: Open file on editor
59 | - **Save**: Save current editor context.
60 | - **Run**: Execute script directly into board
61 | - **Download**: Save script into board
62 | - **Show Terminal**: Switch to terminal view
63 |
64 | #### Side Views
65 |
66 | ###### Symbol bar
67 | This view allows quick access to the symbols in the code on editor
68 |
69 | 
70 |
71 | ###### Snipplets
72 | This view show some code snipplets to speed edition
73 |
74 | 
75 |
76 | #### Terminal
77 |
78 | 
79 |
--------------------------------------------------------------------------------
/bin/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/doc/screenshoot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/doc/screenshoot1.png
--------------------------------------------------------------------------------
/doc/simbolbrowser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/doc/simbolbrowser.png
--------------------------------------------------------------------------------
/doc/snipplets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/doc/snipplets.png
--------------------------------------------------------------------------------
/doc/terminal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/doc/terminal.png
--------------------------------------------------------------------------------
/doc/toolbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/doc/toolbar.png
--------------------------------------------------------------------------------
/doc/toolbar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
201 |
--------------------------------------------------------------------------------
/freeze_setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | """
4 | This setup script build a frozen distribution of the application (with the
5 | python interpreter and 3rd party libraries embedded) for Windows.
6 |
7 | Run the following command to freeze the app (the frozen executable can be
8 | found in the bin folder::
9 |
10 | python freeze_setup.py build
11 |
12 | """
13 | import os
14 | import sys
15 | from cx_Freeze import setup, Executable
16 | import shutil
17 | from pyqode.core.api.syntax_highlighter import get_all_styles
18 | from pyqode.python.backend import server
19 |
20 | # from src.uPyIDE import __version__
21 |
22 | sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
23 | import uPyIDE
24 |
25 | # automatically build when run without arguments
26 | if len(sys.argv) == 1:
27 | sys.argv.append("build")
28 |
29 | # collect pygments styles
30 | pygments_styles = []
31 | for s in get_all_styles():
32 | module = 'pygments.styles.%s' % s.replace('-', '_')
33 | try:
34 | __import__(module)
35 | except ImportError:
36 | pass
37 | else:
38 | pygments_styles.append(module)
39 | print('pygment styles', pygments_styles)
40 |
41 |
42 | # Build options
43 | options = {
44 | "excludes": ["PyQt5.uic.port_v3", "tcltk", "jedi"],
45 | "namespace_packages": ["pyqode"],
46 | "include_msvcr": True,
47 | "build_exe": "bin",
48 | "includes": ["pkg_resources", "PySide.QtCore", 'PySide.QtGui',
49 | 'PySide.QtNetwork', 'pydoc'] + pygments_styles
50 | }
51 |
52 |
53 | # Run the cxFreeze setup
54 | setup(name="uPyIDE",
55 | version='1.0', #__version__,
56 | options={"build_exe": options},
57 | executables=[
58 | Executable(uPyIDE.__file__.replace('.pyc', '.py'),
59 | targetName="uPyIDE.exe",
60 | packages='uPyIDE',
61 | icon='share/uPyIDE/images/uPyIDE.ico',
62 | base="Win32GUI"),
63 | Executable(server.__file__.replace('.pyc', '.py'),
64 | targetName="server.exe")])
65 |
66 |
67 | # NOTE: if you are using PyQt5, you will have to copy libEGL.dll manually
68 | try:
69 | import PyQt5
70 | except ImportError:
71 | pass # pyqt4 or pyqside
72 | else:
73 | shutil.copy(os.path.join(os.path.dirname(PyQt5.__file__), 'libEGL.dll'), 'bin')
74 |
75 |
76 | # cx_freeze has some issues embedding the jedi package, let's do it ourself
77 | import jedi
78 | try:
79 | shutil.copytree(os.path.dirname(jedi.__file__), 'bin/jedi')
80 | except OSError:
81 | pass
82 |
83 | # also copy server.py in order to be able to run on external interpreters
84 | shutil.copy(server.__file__, 'bin')
85 |
--------------------------------------------------------------------------------
/genexe.bat:
--------------------------------------------------------------------------------
1 | \Python34\Scripts\pyinstaller.exe -w -F --distpath=bin -i share\uPyIDE\images\uPyIDE.ico src\uPyIDE.py
2 | \Python34\Scripts\pyinstaller.exe -w -F --distpath=bin -i share\uPyIDE\images\uPyIDE.ico src\server.py
3 |
--------------------------------------------------------------------------------
/mk_install.nsi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/mk_install.nsi
--------------------------------------------------------------------------------
/requeriments.txt:
--------------------------------------------------------------------------------
1 | PySide
2 | pyqode.python
3 | pyserial
4 | pyte
5 | tendo
--------------------------------------------------------------------------------
/share/uPyIDE/fakelibs/pyb.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 28 de ago. de 2016
3 |
4 | @author: martin
5 | '''
6 |
7 |
8 | def delay(milis):
9 | pass
10 |
11 |
12 | class LED:
13 | def __init__(self, led_id):
14 | pass
15 |
16 | def intensity(self, value=0):
17 | pass
18 |
19 | def on(self):
20 | pass
21 |
22 | def off(self):
23 | pass
24 |
25 | def toggle(self):
26 | pass
27 |
28 |
29 | class Switch:
30 | def __init__(self, switch_id):
31 | pass
32 |
33 | def __call__(self):
34 | pass
35 |
36 | def callback(self, func):
37 | pass
38 |
39 |
40 | class UART:
41 | RTS = None
42 | CTS = None
43 |
44 | def __init__(self, uart_id):
45 | pass
46 |
47 | def init(self, baud=115200, bits=8, parity=None, stop=1, timeout=1000,
48 | timeout_char=1000, read_buf_len=64, packet=False):
49 | pass
50 |
51 | def deinit(self):
52 | pass
53 |
54 | def read(self, nbytes=None):
55 | pass
56 |
57 | def readall(self):
58 | pass
59 |
60 | def readline(self):
61 | pass
62 |
63 | def readinto(self, buffer, nbytes=None):
64 | pass
65 |
66 | def any(self):
67 | pass
68 |
69 | def write(self, data):
70 | pass
71 |
72 | def readchar(self):
73 | pass
74 |
75 | def writechar(self):
76 | pass
77 |
78 | def sendbreack(self):
79 | pass
80 |
81 |
82 | class Pin:
83 | IN = None
84 | OUT_PP = None
85 | OUT_OD = None
86 | AF_PP = None
87 | AF_OD = None
88 | ANALOG = None
89 |
90 | PULL_NONE = None
91 | PULL_UP = None
92 | PULL_DOWN = None
93 |
94 | def __init__(self, pin_id):
95 | pass
96 |
97 | def init(self, mode, pull=PULL_NONE, af=-1):
98 | pass
99 |
100 | def value(self, value=None):
101 | pass
102 |
103 | def __str__(self):
104 | pass
105 |
106 | def af(self):
107 | pass
108 |
109 | def gpio(self):
110 | pass
111 |
112 | def mode(self):
113 | pass
114 |
115 | def name(self):
116 | pass
117 |
118 | def names(self):
119 | pass
120 |
121 | def pin(self):
122 | pass
123 |
124 | def port(self):
125 | pass
126 |
127 | def pull(self):
128 | pass
129 |
130 |
131 | class ExtInt:
132 | IRQ_FALLING = None
133 | IRQ_RISING = None
134 | IRQ_RISING_FALLING = None
135 |
136 | def __init__(self, pin, mode, pull, callback):
137 | pass
138 |
139 | def enable(self):
140 | pass
141 |
142 | def disable(self):
143 | pass
144 |
145 | def swint(self):
146 | pass
147 |
148 |
149 | class DAC:
150 | NORMAL = None
151 | CIRCULAR = None
152 |
153 | def __init__(self, dac_line, bits=10):
154 | pass
155 |
156 | def init(self, bits=10):
157 | pass
158 |
159 | def noise(self):
160 | pass
161 |
162 | def triangle(self):
163 | pass
164 |
165 | def write(self, value):
166 | pass
167 |
168 | def write_timed(self, data, frec, *, mode=DAC_NORMAL):
169 | pass
170 |
171 |
172 | class Timer:
173 | def __init__(self, timer_id):
174 | pass
175 |
176 | def init(self, *, frec, prescaler, period):
177 | pass
178 |
179 | def deinit(self):
180 | pass
181 |
182 | def callback(self, fun):
183 | pass
184 |
185 | def interval(self, period, func):
186 | pass
187 |
188 | def timeout(self, period, func):
189 | pass
190 |
191 | def counter(self, value=None):
192 | pass
193 |
194 | def freq(self, value=None):
195 | pass
196 |
197 | def period(self, value=None):
198 | pass
199 |
200 | def prescaler(self, value=None):
201 | pass
202 |
203 | def source_freq(self, value=None):
204 | pass
205 |
206 |
207 | class PWM:
208 | @staticmethod
209 | def set_frecuency(frec):
210 | pass
211 |
212 | def __init__(self, channel):
213 | pass
214 |
215 | def duty_cycle(self, duty):
216 | pass
217 |
218 |
219 | class ADC:
220 | def __init__(self, channel):
221 | pass
222 |
223 | def read(self):
224 | pass
225 |
226 |
227 | class EEPROM:
228 | def __init__(self):
229 | pass
230 |
231 | def read_byte(self):
232 | pass
233 |
234 | def read_int(self):
235 | pass
236 |
237 | def read_float(self):
238 | pass
239 |
240 | def write_byte(self, val):
241 | pass
242 |
243 | def write_int(self, val):
244 | pass
245 |
246 | def write_float(self, val):
247 | pass
248 |
249 | def write(self, val):
250 | pass
251 |
252 | def readall(self):
253 | pass
254 |
255 |
256 | class SPI:
257 | def __init__(self, bits, mode, bitrate):
258 | pass
259 |
260 | def write(self, data, length):
261 | pass
262 |
263 | def read(self, length):
264 | pass
265 |
266 | def readinto(self, buffer):
267 | pass
268 |
269 | def write_read_into(self, wbuff, rbuff):
270 | pass
271 |
272 |
273 | class RTC:
274 | MASK_SEC = 1 << 0
275 | MASK_MIN = 1 << 1
276 | MASK_HR = 1 << 2
277 | MASK_DAY = 1 << 3
278 | MASK_MON = 1 << 4
279 | MASK_YR = 1 << 6
280 | MASK_DOW = 1 << 7
281 |
282 | def __init__(self):
283 | pass
284 |
285 | def datetime(self, dt_tuple=None):
286 | pass
287 |
288 | def alarm_datetime(self, dt_tuple=None, alarmMask=None):
289 | pass
290 |
291 | def alarm_disable(self):
292 | pass
293 |
294 | def callback(self, functor):
295 | pass
296 |
297 | def read_bkp_reg(self, addr):
298 | pass
299 |
300 | def write_bkp_reg(self, addr, value):
301 | pass
302 |
303 | def calibration(self, value):
304 | pass
305 |
306 |
307 | class I2C:
308 | def __init__(self, frec):
309 | pass
310 |
311 | def write(self, data, length):
312 | pass
313 |
314 | def read(self, length):
315 | pass
316 |
317 | def readinto(self, buffer):
318 | pass
319 |
320 | def slave_addr(self, addr=None):
321 | pass
322 |
323 |
--------------------------------------------------------------------------------
/share/uPyIDE/help.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | color: blue;
3 | }
4 |
--------------------------------------------------------------------------------
/share/uPyIDE/help.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | uPyIDE Help
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/share/uPyIDE/help.md:
--------------------------------------------------------------------------------
1 | *** uPyIDE ***
2 |
3 | - TODO
--------------------------------------------------------------------------------
/share/uPyIDE/images/about.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/share/uPyIDE/images/about.png
--------------------------------------------------------------------------------
/share/uPyIDE/images/document-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/share/uPyIDE/images/document-new.png
--------------------------------------------------------------------------------
/share/uPyIDE/images/document-open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/share/uPyIDE/images/document-open.png
--------------------------------------------------------------------------------
/share/uPyIDE/images/document-save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/share/uPyIDE/images/document-save.png
--------------------------------------------------------------------------------
/share/uPyIDE/images/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/share/uPyIDE/images/download.png
--------------------------------------------------------------------------------
/share/uPyIDE/images/run.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/share/uPyIDE/images/run.png
--------------------------------------------------------------------------------
/share/uPyIDE/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/share/uPyIDE/images/splash.png
--------------------------------------------------------------------------------
/share/uPyIDE/images/terminal-out.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/share/uPyIDE/images/terminal-out.png
--------------------------------------------------------------------------------
/share/uPyIDE/images/terminal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/share/uPyIDE/images/terminal.png
--------------------------------------------------------------------------------
/share/uPyIDE/images/uPyIDE.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/share/uPyIDE/images/uPyIDE.ico
--------------------------------------------------------------------------------
/share/uPyIDE/images/uPyIDE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/share/uPyIDE/images/uPyIDE.png
--------------------------------------------------------------------------------
/share/uPyIDE/images/upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martinribelotta/uPyIDE/5d2cd5201a541d7e7e373ead46c8f77add7bf471/share/uPyIDE/images/upload.png
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_ExtInt.py:
--------------------------------------------------------------------------------
1 | # Description ADC Test
2 | import pyb
3 |
4 |
5 | def callBack(line):
6 | print("Pin Interrupt!")
7 | print("Line =", line)
8 |
9 | p = pyb.Pin(0)
10 | p.init(pyb.Pin.OUT_PP, pyb.Pin.PULL_NONE)
11 | print(p)
12 | intr = pyb.ExtInt(p, pyb.ExtInt.IRQ_RISING, pyb.Pin.PULL_NONE, callBack)
13 | print(intr)
14 |
15 | intr.disable()
16 | intr.enable()
17 |
18 | switch1 = pyb.Switch(1)
19 |
20 | while True:
21 | pyb.delay(1000)
22 | print("tick")
23 | if switch1.switch():
24 | int.swint()
25 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_PWM.py:
--------------------------------------------------------------------------------
1 | import pyb
2 |
3 | print("Test PWM")
4 |
5 | print("Setting frequency...")
6 | pyb.PWM.set_frequency(1000)
7 |
8 | print("Creating pwm for out0")
9 | out0 = pyb.PWM(0)
10 |
11 | print("Setting duty cycle to 50%")
12 | out0.duty_cycle(50)
13 | print("Duty cycle :"+str(out0.duty_cycle()))
14 |
15 | out1= pyb.PWM(1)
16 | out1.duty_cycle(25)
17 |
18 | out10= pyb.PWM(10)
19 | out10.duty_cycle(75)
20 |
21 |
22 | print("Main loop")
23 | while True:
24 | pyb.delay(100)
25 |
26 |
27 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_Timers.py:
--------------------------------------------------------------------------------
1 | import pyb
2 | import math
3 |
4 | def callb(timer):
5 | print("Timer tick")
6 | print(timer)
7 |
8 |
9 | print("Test Timers")
10 |
11 |
12 | t2 = pyb.Timer(2)
13 |
14 | print(t2)
15 |
16 | t2.init(freq=1)
17 | t2.callback(callb)
18 |
19 |
20 | while True:
21 | pyb.delay(1000)
22 |
23 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_Timers_2.py:
--------------------------------------------------------------------------------
1 | import pyb
2 |
3 | def callb(timer):
4 | print("Interval interrupt")
5 | print(timer)
6 |
7 | def callbTimeout (timer):
8 | print("Timeout interrupt")
9 | print(timer)
10 |
11 | print("Test Timers")
12 |
13 | t1 = pyb.Timer(1)
14 | t2 = pyb.Timer(2)
15 |
16 | t1.interval(2000,callb)
17 | t2.timeout(5000,callbTimeout)
18 |
19 | t1.freq(1)
20 | print("f t1:"+str(t1.freq()))
21 |
22 | t1.period(204000000)
23 | t1.prescaler(3)
24 | print("period t1:"+str(t1.period()))
25 | print("presc t1:"+str(t1.prescaler()))
26 |
27 | t1.counter(0)
28 | print("counter t1:"+str(t1.counter()))
29 |
30 | print("src freq t1:"+str(t1.source_freq()))
31 |
32 |
33 | while True:
34 | pyb.delay(100)
35 |
36 | #t1.counter(0)
37 | #t1.deinit()
38 |
39 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_dac.py:
--------------------------------------------------------------------------------
1 | # Description: ADC test
2 | import pyb
3 | import math
4 |
5 | print("Test DAC")
6 |
7 | dac = pyb.DAC(1)
8 |
9 | print(dac)
10 |
11 | #dac.triangle(5000)
12 | #dac.noise(5000)
13 |
14 | # sawtooth
15 | #buf = bytearray(200)
16 | #j=0
17 | #for i in range (0,len(buf)/2):
18 | # v = int(i*10)
19 | # buf[j+1] = (v >> 8) & 0xff
20 | # buf[j] = v & 0xff
21 | # j=j+2
22 |
23 | # sine
24 | buf = bytearray(200)
25 | j=0
26 | for i in range (0,len(buf)/2):
27 | v = 512 + int(511 * math.sin(2 * math.pi * i / (len(buf)/2) ) )
28 | buf[j+1] = (v >> 8) & 0xff
29 | buf[j] = v & 0xff
30 | j=j+2
31 |
32 | # output the sine-wave at 400Hz
33 | print("sine created")
34 |
35 | dac.write_timed(buf, 400*(int(len(buf)/2)), mode=pyb.DAC.CIRCULAR)
36 |
37 |
38 | while True:
39 | print("tick")
40 | pyb.delay(1000)
41 | #dac.write(0)
42 | pyb.delay(1000)
43 | #dac.write(512)
44 | pyb.delay(1000)
45 | #dac.write(1013)
46 |
47 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_gc.py:
--------------------------------------------------------------------------------
1 | # Description: Garbage collection test
2 | import pyb
3 | import math
4 |
5 | class Clase:
6 | def __init__(self):
7 | pass
8 |
9 | def miMetodo(self,arg):
10 | print("Mi metodo:"+str(arg))
11 |
12 | c = Clase()
13 |
14 | led1 = pyb.LED(1)
15 | led2 = pyb.LED(2)
16 | led3 = pyb.LED(3)
17 |
18 | switch1 = pyb.Switch(1)
19 | switch2 = pyb.Switch(2)
20 | switch3 = pyb.Switch(3)
21 | switch4 = pyb.Switch(4)
22 |
23 | counter=0
24 | while(True):
25 | print('Estado de pulsadores:')
26 | val = switch1.value()
27 | print('sw1 vale:'+str(val))
28 | val = switch2.value()
29 | print('sw2 vale:'+str(val))
30 | val = switch3.value()
31 | print('sw3 vale:'+str(val))
32 | val = switch4.value()
33 | print('sw4 vale:'+str(val))
34 |
35 | counter=counter+1
36 | print('Modificacion de leds:'+str(counter))
37 | print('Enciendo')
38 | led1.on()
39 | pyb.delay(100);
40 | print('Apago')
41 | led1.off()
42 | pyb.delay(100);
43 |
44 | #print('Enciendo')
45 | #led2.on()
46 | #pyb.delay(1000);
47 | #print('Apago')
48 | #led2.off()
49 | #pyb.delay(1000);
50 |
51 | #print('Enciendo')
52 | #led3.on()
53 | #pyb.delay(1000);
54 | #print('Apago')
55 | #led3.off()
56 | #pyb.delay(1000);
57 |
58 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_gpios.py:
--------------------------------------------------------------------------------
1 | import pyb
2 |
3 | p = pyb.Pin(8)
4 | p.init(pyb.Pin.OUT_PP,pyb.Pin.PULL_NONE)
5 | print(p)
6 |
7 | while True:
8 | #p.value(True)
9 | p.high()
10 | print("value:"+str(p.value()))
11 |
12 | pyb.delay(1000)
13 |
14 | #p.value(False)
15 | p.low()
16 | print("value:"+str(p.value()))
17 |
18 | pyb.delay(1000)
19 |
20 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_perf.py:
--------------------------------------------------------------------------------
1 | import pyb
2 |
3 | @micropython.native
4 | def performanceTest():
5 | d = 10000
6 | print("start. Wait for %d milis" % d)
7 | millis = pyb.millis
8 | endTime = millis() + d
9 | count = 0
10 | while millis() < endTime:
11 | count += 1
12 | print("Count: ", count)
13 |
14 | while(True):
15 | performanceTest()
16 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_perf2.py:
--------------------------------------------------------------------------------
1 | import pyb
2 |
3 | led1 = pyb.LED(1)
4 | led2 = pyb.LED(2)
5 | led3 = pyb.LED(3)
6 |
7 | #@micropython.native
8 | def speed():
9 | for i in range(1000000):
10 | led1.on()
11 | led1.off()
12 |
13 |
14 | speed()
15 |
16 |
17 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_python.py:
--------------------------------------------------------------------------------
1 | import math
2 | import collections
3 |
4 | class Clase:
5 | def __init__(self):
6 | pass
7 |
8 | def miMetodo(self,arg):
9 | print("Mi metodo:"+str(arg))
10 |
11 | #Test class
12 | c = Clase()
13 | c.miMetodo(56)
14 |
15 | # Test bytearray
16 | ba = bytearray()
17 | ba.append(33)
18 |
19 | # Test slice
20 | values = [100, 200, 300, 400, 500]
21 | slice = values[2:-1]
22 | print(slice)
23 | values = "Hello World"
24 | evens = values[:2]
25 | print(evens)
26 |
27 | # Test math
28 | s = math.sqrt(2)
29 | print("sqrt(2)="+str(s))
30 |
31 | # Test array
32 | from array import *
33 | x=array('f',[0.0,1.0,2.0])
34 | print(x)
35 |
36 | # Test Set
37 | print("Test de Sets")
38 | x = [1, 1, 2, 2, 2, 2, 2, 3, 3]
39 | s = set(x)
40 | print(s)
41 |
42 | #Test tuples
43 | a,b = 4,6
44 | print("a:"+str(a)+" b:"+str(b))
45 | a,b,c = 4,5,6
46 | print("a:"+str(a)+" b:"+str(b)+" c:"+str(c))
47 |
48 | #Test collection
49 | Person = collections.namedtuple("Person", ["name", "age", "gender"] )
50 | bob = Person(name='Bob', age=30, gender='male')
51 | print(bob)
52 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_rgb.py:
--------------------------------------------------------------------------------
1 | import pyb
2 | import math
3 |
4 | print("Test led RGB")
5 |
6 |
7 | ledR = pyb.LED(4)
8 | ledG = pyb.LED(5)
9 | ledB = pyb.LED(6)
10 |
11 | s0 = bytearray(100)
12 | for i in range (0,len(s0)):
13 | s0[i] = 8 + int(7 * math.sin(2 * math.pi * i / (len(s0)) ) )
14 |
15 | s1 = bytearray(100)
16 | for i in range (0,len(s1)):
17 | s1[i] = 8 + int(7 * math.sin( (1/3)*math.pi + 2 * math.pi * i / (len(s1)) ) )
18 |
19 | s2 = bytearray(100)
20 | for i in range (0,len(s2)):
21 | s2[i] = 8 + int(7 * math.sin( (2/3)*math.pi + 2 * math.pi * i / (len(s2)) ) )
22 |
23 | t=0
24 | while True:
25 | pyb.delay(30)
26 | val = s0[t]
27 | ledR.intensity(val)
28 | #print("intensity:"+str(ledR.intensity()))
29 |
30 | val=s1[t]
31 | ledG.intensity(val)
32 |
33 | val=s2[t]
34 | ledB.intensity(val)
35 |
36 | t+=1
37 | if t==100:
38 | t=0
39 |
40 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_uart.py:
--------------------------------------------------------------------------------
1 | import pyb
2 |
3 | uart = pyb.UART(0)
4 |
5 | #uart.init(115200,bits=8, parity=None, stop=1,timeout=0, timeout_char=1000, read_buf_len=64,packet_mode=True,packet_end_char=ord('o'))
6 | uart.init(115200,bits=8, parity=None, stop=1,timeout=1000, timeout_char=1000, read_buf_len=64)
7 | #uart.init(115200)
8 |
9 | print(uart)
10 |
11 | while True:
12 | uart.write("A")
13 | pyb.delay(1000)
14 | if uart.any():
15 | print("hay data:")
16 |
17 | # Test 1
18 | #d = uart.readchar()
19 | #print(str(d))
20 | #uart.writechar(d)
21 |
22 | #Test 2
23 | #data = uart.read()
24 | data = uart.readall()
25 | #data = uart.readline()
26 | #data = uart.read(3)
27 |
28 | #buf = bytearray(256)
29 | #bytes = uart.readinto(buf)
30 | #if bytes != 0:
31 | # print(buf)
32 |
33 | print(data)
34 | uart.write(data)
35 |
36 | uart.deinit()
37 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/Main_welcome.py:
--------------------------------------------------------------------------------
1 | print("Welcome to Micropython on EDU-CIAA-NXP")
2 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/snipplet_class.py:
--------------------------------------------------------------------------------
1 | # Description: Class declaration
2 | class NAME(object):
3 | def __init__(self):
4 | pass
5 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/snipplet_iterator.py:
--------------------------------------------------------------------------------
1 | # Description: Object iterator
2 | class firstn(object):
3 | def __init__(self, n):
4 | self.n = n
5 | self.num = 0
6 |
7 | def __iter__(self):
8 | return self
9 |
10 | # Python 3 compatibility
11 | def __next__(self):
12 | return self.next()
13 |
14 | def next(self):
15 | if self.num < self.n:
16 | cur, self.num = self.num, self.num+1
17 | return cur
18 | else:
19 | raise StopIteration()
20 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/snipplet_main.py:
--------------------------------------------------------------------------------
1 | # Description: Main of module
2 | if __name__ == '__main__':
3 | pass
4 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/snipplet_open.py:
--------------------------------------------------------------------------------
1 | # Description: open file
2 | with open('NAME', 'rwa+') as f:
3 | pass
4 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/snipplet_try.py:
--------------------------------------------------------------------------------
1 | # Description: Try/Except/Finally/Else block
2 | try:
3 | pass
4 | except Exception as e:
5 | print(e)
6 | else:
7 | pass
8 | finally:
9 | pass
10 |
--------------------------------------------------------------------------------
/share/uPyIDE/snipplet/snipplets.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
15 |
16 |
32 |
33 |
48 |
49 |
--------------------------------------------------------------------------------
/src/images/about.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
500 |
--------------------------------------------------------------------------------
/src/images/document-new.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
423 |
--------------------------------------------------------------------------------
/src/images/document-open.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
335 |
--------------------------------------------------------------------------------
/src/images/document-save.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
369 |
--------------------------------------------------------------------------------
/src/images/download.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
303 |
--------------------------------------------------------------------------------
/src/images/run.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
299 |
--------------------------------------------------------------------------------
/src/images/terminal-out.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
398 |
--------------------------------------------------------------------------------
/src/images/terminal.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
397 |
--------------------------------------------------------------------------------
/src/images/upload.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
300 |
--------------------------------------------------------------------------------
/src/myDef.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | """
4 | Created on Tue Jul 25 00:22:16 2017
5 |
6 | @author: coolshou
7 | """
8 | import pyqode_i18n
9 | import locale
10 |
11 | def i18n(s):
12 | lang, encode = locale.getdefaultlocale()
13 | return pyqode_i18n.tr(s, lang=lang)
14 |
15 |
--------------------------------------------------------------------------------
/src/pyqode_i18n.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | _dict = {
4 | "es": {
5 | "Edu CIAA MicroPython": "Edu CIAA MicroPython",
6 | "Open": "Abrir",
7 | "Save": "Guardar",
8 | "New": "Nuevo",
9 | "Run": "Ejecutar",
10 | "About": "Acerca de...",
11 | "Help": "Ayuda",
12 | "Terminal": "Terminal",
13 | "To Editor": "Al Editor",
14 | "Outline": "Simbolos",
15 | "Snipplets": "Trozos de codigo",
16 | "Download": "Grabar",
17 | "Undo": "Deshacer",
18 | "Redo": "Rehacer",
19 | "Cut": "Cortar",
20 | "Copy": "Copiar",
21 | "Paste": "Pegar",
22 | "Duplicate line": "Duplicar Linea",
23 | "Delete": "Borrar",
24 | "Indent": "Identar",
25 | "Un-indent": "Desidentar",
26 | "Go to line": "Ir a la linea",
27 | "Search": "Buscar",
28 | "Select": "Seleccionar",
29 | "Case": "Capitalización",
30 | "Go to assignments": "Ir a...",
31 | "Comment/Uncomment": "Comentar/Descomentar",
32 | "Show documentation": "Ver documentación",
33 | "Folding": "Plegar codigo",
34 | "Encodings": "Codificación",
35 | "Select word": "Seleccionar palabra",
36 | "Select extended word": "Seleccionar palabra extendida",
37 | "Matched select": "[TODO] Matched select",
38 | "Select line": "Seleccionar linea",
39 | "Select all": "Seleccionar todo",
40 | "Search and replace": "Buscar y reemplazar",
41 | "Find next": "Encontrar siguiente",
42 | "Find previous": "Encontrar anterior",
43 | "Convert to lower case": "Convertir a minusculas",
44 | "Convert to UPPER CASE": "Convertir a MAYUSCULAS",
45 | "Question": "Pregunta",
46 | "Document was modify": "El documento se ha modificado",
47 | "Save changes?": "¿Guardar los cambios?",
48 | "Save File": "Guardar Archivo",
49 | "Open File": "Abrir Archivo",
50 | "Python files (*.py);;All files (*)": "Archivos Python (*.py);;"
51 | "Todos los archivos (*)",
52 | "Serial Port:": "Puerto Serial:",
53 | "NewFile.py (%d)": "Nuevo.py (%d)",
54 | "Remote Name": "Nombre Remoto",
55 | "Select Serial Port": "Seleccionar Puerto Serie"
56 | },
57 | "zh_TW": {
58 | "Edu CIAA MicroPython": "Edu CIAA MicroPython",
59 | "Open": "開啟",
60 | "Save": "儲存",
61 | "New": "新檔案",
62 | "Run": "執行",
63 | "About": "關於...",
64 | "Help": "幫助",
65 | "Terminal": "終端機",
66 | "To Editor": "到編輯器",
67 | "Outline": "大綱",
68 | "Snipplets": "片段",
69 | "Download": "下載",
70 | "Undo": "復原",
71 | "Redo": "重做",
72 | "Cut": "剪下",
73 | "Copy": "複製",
74 | "Paste": "貼上",
75 | "Duplicate line": "複製此行",
76 | "Delete": "刪除",
77 | "Indent": "縮排",
78 | "Un-indent": "取消縮排",
79 | "Go to line": "Go to line",
80 | "Search": "尋找",
81 | "Select": "選取",
82 | "Case": "Case",
83 | "Go to assignments": "Go to assignments",
84 | "Comment/Uncomment": "註解/取消註解",
85 | "Show documentation": "顯示文件",
86 | "Folding": "折疊",
87 | "Encodings": "編碼",
88 | "Select word": "選取字",
89 | "Select extended word": "選取延伸字",
90 | "Matched select": "[TODO] 匹配選取",
91 | "Select line": "選取行",
92 | "Select all": "選取全部",
93 | "Search and replace": "尋找和取代",
94 | "Find next": "找下一個",
95 | "Find previous": "找前一個",
96 | "Convert to lower case": "轉為小寫",
97 | "Convert to UPPER CASE": "轉為大寫",
98 | "Question": "問題",
99 | "Document was modify": "文件已經被修改",
100 | "Save changes?": "儲存修改?",
101 | "Save File": "儲存檔案",
102 | "Open File": "開啟檔案",
103 | "Python files (*.py);;All files (*)": "Python 檔案(*.py);;"
104 | "所有檔案 (*)",
105 | "Serial Port:": "序列埠:",
106 | "NewFile.py (%d)": "新檔案.py (%d)",
107 | "Remote Name": "遠端名稱",
108 | "Select Serial Port": "選取序列埠",
109 | "Device files": "裝置的檔案",
110 | "Refresh": "重新整理",
111 | "Device": "裝置",
112 | "Download to Device": "上傳到裝置"
113 | }
114 | }
115 |
116 |
117 | def tr(text, lang="es"):
118 | if lang in _dict and text in _dict[lang].keys():
119 | return _dict[lang][text]
120 | else:
121 | return "*{}".format(text)
122 |
--------------------------------------------------------------------------------
/src/server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | """
4 | Main server script for a pyqode.python backend. You can directly use this
5 | script in your application if it fits your needs or use it as a starting point
6 | for writing your own server.
7 |
8 | ::
9 |
10 | usage: server.py [-h] [-s [SYSPATH [SYSPATH ...]]] port
11 |
12 | positional arguments:
13 | port the local tcp port to use to run the server
14 |
15 | optional arguments:
16 | -h, --help show this help message and exit
17 | -s [SYSPATH [SYSPATH ...]], --syspath [SYSPATH [SYSPATH ...]]
18 |
19 | """
20 | import argparse
21 | import logging
22 | import sys
23 |
24 |
25 | if __name__ == '__main__':
26 | """
27 | Server process' entry point
28 | """
29 | logging.basicConfig()
30 | # setup argument parser and parse command line args
31 | parser = argparse.ArgumentParser()
32 | parser.add_argument("port", help="the local tcp port to use to run "
33 | "the server")
34 | parser.add_argument('-s', '--syspath', nargs='*')
35 | args = parser.parse_args()
36 |
37 | # add user paths to sys.path
38 | if args.syspath:
39 | for path in args.syspath:
40 | print(('append path %s to sys.path' % path))
41 | sys.path.append(path)
42 |
43 | from pyqode.core import backend
44 | from pyqode.python.backend.workers import JediCompletionProvider
45 |
46 | # setup completion providers
47 | backend.CodeCompletionWorker.providers.append(JediCompletionProvider())
48 | backend.CodeCompletionWorker.providers.append(
49 | backend.DocumentWordsProvider())
50 |
51 | # starts the server
52 | backend.serve_forever(args)
53 |
--------------------------------------------------------------------------------
/src/termWidget.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import glob
3 | import pyte
4 | import serial
5 | import sys
6 | import threading
7 | import time
8 |
9 | import pyqode.qt.QtWidgets as QtWidgets
10 | import pyqode.qt.QtCore as QtCore
11 | import pyqode.qt.QtGui as QtGui
12 | from PyQt5.Qt import QApplication
13 |
14 | from myDef import i18n
15 |
16 |
17 | def serial_ports():
18 | """ Lists serial port names
19 | :raises EnvironmentError:
20 | On unsupported or unknown platforms
21 | :returns:
22 | A list of the serial ports available on the system
23 | """
24 | if sys.platform.startswith('win'):
25 | ports = ['COM%s' % (i + 1) for i in range(256)]
26 | elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
27 | # this excludes your current terminal "/dev/tty"
28 | ports = glob.glob('/dev/tty[A-Za-z]*')
29 | elif sys.platform.startswith('darwin'):
30 | ports = glob.glob('/dev/tty.*')
31 | else:
32 | raise EnvironmentError('Unsupported platform')
33 | result = []
34 | for port in ports:
35 | try:
36 | s = serial.Serial(port)
37 | s.close()
38 | result.append(port)
39 | except (OSError, serial.SerialException):
40 | pass
41 | return result
42 |
43 |
44 | class Terminal(QtWidgets.QWidget):
45 | '''
46 | classdocs
47 | '''
48 | def __init__(self, parent=None):
49 | '''
50 | Constructor
51 | '''
52 | super(self.__class__, self).__init__(parent)
53 | self.setFont(QtGui.QFont({
54 | 'win32': 'Consolas',
55 | 'linux': 'Monospace',
56 | 'darwin': 'Andale Mono'
57 | }.get(sys.platform, 'Courier'), 10))
58 | self.setFocusPolicy(QtCore.Qt.ClickFocus)
59 | self.setStyleSheet("background-color : black; color : #cccccc;")
60 | self._workers = []
61 | self._serial = None
62 | self._thread = None
63 | self._stream = pyte.Stream()
64 | self._vt = pyte.Screen(80, 24)
65 | self._stream.attach(self._vt)
66 | self._workers.append(self._processText)
67 | self._stop = threading.Event()
68 |
69 |
70 | def mousePressEvent(self, QMouseEvent):
71 | if QMouseEvent.button() == QtCore.Qt.LeftButton:
72 | #print("Left Button Clicked")
73 | pass
74 | elif QMouseEvent.button() == QtCore.Qt.RightButton:
75 | #print("Right Button Clicked")
76 | self.rightMenu(QMouseEvent.pos())
77 |
78 |
79 | def rightMenu(self, position):
80 | menu = QtWidgets.QMenu()
81 | pasteAction = menu.addAction(i18n("Paste"))
82 | pasteAction.triggered.connect(self.paste)
83 | if not QApplication.clipboard().mimeData().hasText():
84 | pasteAction.setEnabled(False)
85 | menu.exec_(self.mapToGlobal(position))
86 |
87 | def paste(self):
88 | clipText = QApplication.clipboard().text()
89 | if clipText:
90 | self._serial.write(clipText.encode())
91 |
92 |
93 | def resizeEvent(self, event):
94 | charSize = self.textRect(' ').size()
95 | lines = int(event.size().height() / charSize.height())
96 | columns = int(event.size().width() / charSize.width())
97 | self._vt.resize(lines, columns)
98 | self._vt.reset()
99 |
100 | def focusNextPrevChild(self, n):
101 | return False
102 |
103 | def close(self):
104 | self._stop.set()
105 | if self._thread and self._thread.isAlive():
106 | self._thread.join()
107 | self._thread = None
108 | if self._serial:
109 | self._serial.close()
110 |
111 | def open(self, port, speed):
112 | self._stopThread()
113 | if type(self._serial) is serial.Serial:
114 | self._serial.close()
115 | try:
116 | self._serial = serial.Serial(port, speed, timeout=0.5)
117 | self._startThread()
118 | return True
119 | except serial.SerialException as e:
120 | print(e)
121 | return False
122 |
123 | def remoteExec(self, cmd, interceptor=None):
124 | if interceptor:
125 | self._workers.append(interceptor)
126 | cmd_b = cmd if isinstance(cmd, bytes) else bytes(cmd, encoding='utf8')
127 | # write command
128 | for i in range(0, len(cmd_b), 256):
129 | self._serial.write(cmd_b[i:min(i + 256, len(cmd_b))])
130 | time.sleep(0.01)
131 |
132 | def _stopThread(self):
133 | self._stop.set()
134 | if self._thread and self._thread.isAlive():
135 | self._thread.join()
136 | self._thread = None
137 |
138 | def _startThread(self):
139 | self._stop.clear()
140 | self._thread = threading.Thread(target=self._readThread)
141 | self._thread.setDaemon(1)
142 | self._thread.start()
143 |
144 | def _readThread(self):
145 | try:
146 | while not self._stop.is_set():
147 | text = self._serial.read(self._serial.inWaiting() or 1)
148 | if text:
149 | self._workers = [w for w in self._workers if not w(text)]
150 | except Exception as e:
151 | print(e)
152 |
153 | def _processText(self, text):
154 | self._stream.feed(text.decode(errors='ignore'))
155 | self.update()
156 | return False
157 |
158 | def focusInEvent(self, event):
159 | self.repaint()
160 |
161 | def focusOutEvent(self, event):
162 | self.repaint()
163 |
164 | def paintEvent(self, event):
165 | p = QtGui.QPainter()
166 | p.begin(self)
167 | pal = self.palette()
168 | p.fillRect(QtCore.QRect(QtCore.QPoint(), self.size()),
169 | pal.color(pal.Background))
170 | textSize = self.textRect(' ' * self._vt.columns).size()
171 | bound = QtCore.QRect(QtCore.QPoint(), textSize)
172 | flags = QtCore.Qt.AlignLeft | QtCore.Qt.AlignBottom
173 | for line in self._vt.display:
174 | p.drawText(bound, flags, line)
175 | bound.translate(0, bound.height())
176 | if self.hasFocus():
177 | p.fillRect(self.cursorRect(), pal.color(pal.Foreground))
178 | else:
179 | p.drawRect(self.cursorRect())
180 | p.end()
181 |
182 | def textRect(self, text):
183 | textSize = QtGui.QFontMetrics(self.font()).size(0, text)
184 | return QtCore.QRect(QtCore.QPoint(), textSize)
185 |
186 | def cursorRect(self):
187 | r = self.textRect(' ')
188 | r.moveTopLeft(QtCore.QPoint(0, 0) +
189 | QtCore.QPoint(self._vt.cursor.x * r.width(),
190 | self._vt.cursor.y * r.height()))
191 | return r
192 |
193 | def keyPressEvent(self, event):
194 | if self._serial and self._serial.isOpen():
195 | try:
196 | text = {
197 | QtCore.Qt.Key_Tab: lambda x: b"\t",
198 | QtCore.Qt.Key_Backspace: lambda x: b"\x7f",
199 | QtCore.Qt.Key_Up: lambda x: b"\033[A",
200 | QtCore.Qt.Key_Down: lambda x: b"\033[B",
201 | QtCore.Qt.Key_Left: lambda x: b"\033[D",
202 | QtCore.Qt.Key_Right: lambda x: b"\033[C",
203 | }[event.key()](event.key())
204 | except KeyError:
205 | text = bytes(event.text(), 'utf-8')
206 | if text:
207 | self._serial.write(text)
208 | event.accept()
209 |
210 |
211 | def selectPort():
212 | d = QtWidgets.QDialog()
213 | l = QtWidgets.QVBoxLayout(d)
214 | combo = QtWidgets.QComboBox(d)
215 | combo.addItems(serial_ports())
216 | ok = QtWidgets.QPushButton("Ok", d)
217 | ok.clicked.connect(d.close)
218 | l.addWidget(combo)
219 | l.addWidget(ok)
220 | d.exec_()
221 | return combo.currentText()
222 |
223 |
224 | def main():
225 | app = QtWidgets.QApplication(sys.argv)
226 | w = Terminal()
227 | w.resize(640, 480)
228 | w.show()
229 | w.open(selectPort(), 115200)
230 | w.remoteExec('\x04')
231 | app.exec_()
232 |
233 | if __name__ == "__main__":
234 | main()
235 |
--------------------------------------------------------------------------------
/src/uPyIDE.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import tendo.singleton
3 | import os
4 | import re
5 | import sys
6 | import glob
7 | import collections
8 |
9 | import pyqode.python.backend.server as server
10 | import pyqode.python.widgets as widgets
11 | import pyqode.core.widgets as wcore
12 | import pyqode.qt.QtCore as QtCore
13 | import pyqode.qt.QtWidgets as QtWidgets
14 | import pyqode.qt.QtGui as QtGui
15 | import pyqode_i18n
16 | import termWidget
17 | import xml.etree.ElementTree as ElementTree
18 |
19 | import markdown
20 |
21 | from myDef import i18n
22 | # from docutils.parsers.rst.directives import path
23 |
24 |
25 | me = tendo.singleton.SingleInstance()
26 |
27 | __version__ = '1.0'
28 |
29 |
30 |
31 | def executable_path():
32 | if hasattr(sys, 'frozen'):
33 | return os.path.dirname(sys.executable)
34 | else:
35 | return os.path.dirname(sys.argv[0])
36 |
37 |
38 | def share():
39 | return os.path.abspath(os.path.join(executable_path(),
40 | '..', 'share', 'uPyIDE'))
41 |
42 |
43 | def fakelibs():
44 | return os.path.join(share(), 'fakelibs')
45 |
46 |
47 | def icon(name):
48 | path = os.path.join(share(), 'images', '{}.png'.format(name))
49 | return QtGui.QIcon(path)
50 |
51 |
52 | def backend_interpreter():
53 | if getattr(sys, 'frozen', False):
54 | return ''
55 | else:
56 | return sys.executable
57 |
58 |
59 | def completion_server():
60 | if getattr(sys, 'frozen', False):
61 | server_path = os.path.join(executable_path(), 'server.exe')
62 | print(server_path)
63 | return server_path
64 | else:
65 | return server.__file__
66 |
67 |
68 | def about_pixmap():
69 | return QtGui.QPixmap(os.path.join(share(), 'images', 'splash.png'))
70 |
71 |
72 | class WidgetSpacer(QtWidgets.QWidget):
73 | def __init__(self, parent, wmax=None):
74 | super(WidgetSpacer, self).__init__(parent)
75 | self.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
76 | QtWidgets.QSizePolicy.Expanding)
77 | if wmax:
78 | self.setMaximumWidth(wmax)
79 |
80 |
81 | class PortSelector(QtWidgets.QComboBox):
82 | def __init__(self, parent):
83 | super(self.__class__, self).__init__(parent)
84 | self.widget = parent
85 | portList = termWidget.serial_ports()
86 | print(portList)
87 | self.addItems(portList)
88 | self.currentIndexChanged.connect(self.onChange)
89 | self.setCurrentIndex(0)
90 | # self.onChange(0)
91 |
92 | @QtCore.Slot(int)
93 | def onChange(self, n):
94 | if self.currentText():
95 | port = self.currentText()
96 | self.widget.setPort(port)
97 |
98 |
99 | class SnipplerWidget(QtWidgets.QDockWidget):
100 | def __init__(self, parent):
101 | super(SnipplerWidget, self).__init__(i18n('Snipplets'), parent)
102 | self.setWindowTitle(i18n("Snipplets"))
103 | self.snippletView = QtWidgets.QListWidget(self)
104 | self.setWidget(self.snippletView)
105 | self.loadSnipplets()
106 | self.snippletView.itemDoubleClicked.connect(self._insertToParent)
107 |
108 | def _insertToParent(self, item):
109 | if self.parent().tabber.active_editor:
110 | self.parent().tabber.active_editor.insertPlainText(item.toolTip())
111 |
112 | def addSnipplet(self, description, contents):
113 | item = QtWidgets.QListWidgetItem(self.snippletView)
114 | item.setText(description)
115 | item.setToolTip(contents)
116 |
117 | def loadSnippletFrom(self, inp):
118 | xml = ElementTree.fromstring(inp) if type(inp) is str else \
119 | ElementTree.parse(inp).getroot()
120 | for child in xml:
121 | self.addSnipplet(child.attrib["name"], child.text)
122 |
123 | def loadCodeSnipplet(self, source):
124 | with open(source) as f:
125 | s = f.read()
126 | r = re.compile(r'^# Description: (.*)[\r\n]*')
127 | description = ''.join(re.findall(r, s))
128 | contents = re.sub(r, '', s)
129 | if description and contents:
130 | self.addSnipplet(description, contents)
131 |
132 | @QtCore.Slot()
133 | def loadSnipplets(self):
134 | self.snippletView.setStyleSheet('''QToolTip {
135 | font-family: "monospace";
136 | }''')
137 | with open(os.path.join(share(), 'snipplet', 'snipplets.xml')) as f:
138 | self.loadSnippletFrom(f)
139 | snipplet_glob = os.path.join(share(), 'snipplet', '*.py')
140 | for source in glob.glob(snipplet_glob):
141 | self.loadCodeSnipplet(source)
142 |
143 | class DeviceFilesWidget(QtWidgets.QDockWidget):
144 | def __init__(self, parent):
145 | super(DeviceFilesWidget, self).__init__(i18n('Device files'), parent)
146 | self.setWindowTitle(i18n("Device files"))
147 | widget = QtWidgets.QWidget(self)
148 | vlayout = QtWidgets.QVBoxLayout()
149 | self.toolbar = QtWidgets.QToolBar(self)
150 | self.toolbar.addAction(i18n("Refresh"), self.loadRemoteFiles)
151 | self.toolbar.addAction(icon("download"), i18n("Download to Device"), self.downloadFile)
152 | self.filesView = QtWidgets.QTreeWidget(self)
153 | self.filesView.header().close()
154 | self.deviceItem = QtWidgets.QTreeWidgetItem(0)
155 | self.deviceItem.setText(0,i18n("Device"))
156 | self.filesView.addTopLevelItem(self.deviceItem)
157 | vlayout.addWidget(self.toolbar)
158 | vlayout.addWidget(self.filesView)
159 | widget.setLayout(vlayout)
160 | self.setWidget(widget)
161 |
162 | @QtCore.Slot()
163 | def loadRemoteFiles(self):
164 | print('TODO: load Remote File list')
165 | pass
166 | @QtCore.Slot()
167 | def downloadFile(self):
168 | #print(self.parent().windowTitle())
169 | print('TODO: download selected File')
170 | pass
171 |
172 | class MainWindow(QtWidgets.QMainWindow):
173 | onListDir = QtCore.Signal(str)
174 |
175 | def __init__(self):
176 | QtWidgets.QMainWindow.__init__(self)
177 | self.setWindowTitle(i18n("Edu CIAA MicroPython"))
178 | self.cwd = QtCore.QDir.homePath()
179 | self.tabber = wcore.TabWidget(self)
180 | self.term = termWidget.Terminal(self)
181 | self.outline = widgets.PyOutlineTreeWidget()
182 | self.dock_outline = QtWidgets.QDockWidget(i18n('Outline'))
183 | self.dock_outline.setWidget(self.outline)
184 | self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dock_outline)
185 | self.snippler = SnipplerWidget(self)
186 | self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.snippler)
187 | self.deviceFiles = DeviceFilesWidget(self)
188 | self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.deviceFiles)
189 | self.stack = QtWidgets.QStackedWidget(self)
190 | self.stack.addWidget(self.tabber)
191 | self.stack.addWidget(self.term)
192 | self.setCentralWidget(self.stack)
193 | self.makeAppToolBar()
194 | self.resize(1024, 600)
195 | self.onListDir.connect(lambda l: self._showDir(l))
196 | self.tabber.currentChanged.connect(self.actualizeOutline)
197 | self.fileNew()
198 | self.portSelector.onChange(0)
199 |
200 | def actualizeOutline(self, n):
201 | self.outline.set_editor(self.tabber.active_editor)
202 | self.i18n()
203 |
204 | def i18n(self, actions=None):
205 | if not self.tabber.active_editor:
206 | return
207 | if not actions:
208 | actions = self.tabber.active_editor.actions()
209 | for action in actions:
210 | if not action.isSeparator():
211 | action.setText(pyqode_i18n.tr(action.text()))
212 | if action.menu():
213 | self.i18n(action.menu().actions())
214 |
215 | def terminate(self):
216 | self.term.close()
217 |
218 | def makeAppToolBar(self):
219 | bar = QtWidgets.QToolBar('Toolbar', self)
220 | bar.setIconSize(QtCore.QSize(48, 48))
221 | bar.addAction(icon("document-new"), i18n("New"), self.fileNew)
222 | bar.addAction(icon("document-open"), i18n("Open"), self.fileOpen)
223 | bar.addAction(icon("document-save"), i18n("Save"), self.fileSave)
224 | bar.addWidget(WidgetSpacer(self))
225 | bar.addWidget(QtWidgets.QLabel(i18n("Serial Port:")))
226 | bar.addWidget(WidgetSpacer(self, 12))
227 | self.portSelector = PortSelector(self)
228 | bar.addWidget(self.portSelector)
229 | self.portSelector.setToolTip(i18n("Select Serial Port"))
230 | self.runAction = bar.addAction(icon("run"), i18n("Run"), self.progRun)
231 | self.runAction.setEnabled(False)
232 | self.dlAction = bar.addAction(icon("download"), i18n("Download"),
233 | self.progDownload)
234 | self.dlAction.setEnabled(False)
235 | self.termAction = bar.addAction(icon("terminal"), i18n("Terminal"),
236 | self.openTerm)
237 | self.termAction.setEnabled(False)
238 | self.termAction.setCheckable(True)
239 | bar.addAction(icon("about"), i18n("Help"), self.showhelp)
240 | self.addToolBar(bar)
241 |
242 | def _htmlhelp(self):
243 | return os.path.abspath(os.path.join(share(), 'help.html'))
244 |
245 | def _mdhelp(self):
246 | return os.path.abspath(os.path.join(share(), 'help.md'))
247 |
248 | def _cssfile(self):
249 | return os.path.abspath(os.path.join(share(), 'help.css'))
250 |
251 | def showhelp(self):
252 | dlg = QtWidgets.QDialog(self)
253 | dlg.setWindowTitle(i18n('Help'))
254 | l = QtWidgets.QVBoxLayout(dlg)
255 | l.setContentsMargins(6, 6, 6, 6)
256 | tabWidget = QtWidgets.QTabWidget(dlg)
257 | buttonBar = QtWidgets.QDialogButtonBox(dlg)
258 | buttonBar.addButton(QtWidgets.QDialogButtonBox.Close)
259 | buttonBar.accepted.connect(dlg.close)
260 | buttonBar.rejected.connect(dlg.close)
261 | l.addWidget(tabWidget)
262 | l.addWidget(buttonBar)
263 | tv = QtWidgets.QTextBrowser(tabWidget)
264 | image = QtWidgets.QLabel(tabWidget)
265 | image.setPixmap(about_pixmap())
266 | tabWidget.addTab(image, i18n('About'))
267 | tabWidget.addTab(tv, i18n('Help'))
268 | with open(self._cssfile()) as f:
269 | tv.document().setDefaultStyleSheet(f.read())
270 | try:
271 | with open(self._mdhelp()) as f:
272 | tv.document().setHtml(markdown.markdown(f.read()))
273 | except:
274 | try:
275 | with open(self._htmlhelp()) as f:
276 | tv.document().setHtml(f.read())
277 | except:
278 | tv.setHtml("No help")
279 | dlg.exec_()
280 |
281 | def terminalMenu(self):
282 | m = QtWidgets.QMenu(self)
283 | g = QtWidgets.QActionGroup(m)
284 | g.triggered.connect(lambda a: self.setPort(a.text()))
285 | for s in termWidget.serial_ports():
286 | a = m.addAction(s)
287 | g.addAction(a)
288 | a.setCheckable(True)
289 | if g.actions():
290 | g.actions()[0].setChecked(True)
291 | self.setPort(g.actions()[0].text())
292 | return m
293 |
294 | def setPort(self, port):
295 | en = self.term.open(port, 115200)
296 | [i.setEnabled(en) for i in (self.dlAction,
297 | self.runAction,
298 | self.termAction)]
299 |
300 | def closeEvent(self, event):
301 | self.tabber.closeEvent(event)
302 | if event.isAccepted():
303 | self.terminate()
304 |
305 | def createEditor(self):
306 | return widgets.PyCodeEdit(interpreter=backend_interpreter(),
307 | server_script=completion_server(),
308 | args=['-s', fakelibs()])
309 |
310 | def fileNew(self):
311 | code_edit = self.createEditor()
312 | i = self.tabber.add_code_edit(code_edit, i18n("NewFile.py (%d)"))
313 | self.tabber.setCurrentIndex(i)
314 |
315 | def dirtySaveDischartCancel(self):
316 | d = QtWidgets.QMessageBox()
317 | d.setWindowTitle(i18n("Question"))
318 | d.setText(i18n("Document was modify"))
319 | d.setInformativeText(i18n("Save changes?"))
320 | d.setIcon(QtWidgets.QMessageBox.Question)
321 | d.setStandardButtons(QtWidgets.QMessageBox.Save |
322 | QtWidgets.QMessageBox.Discard |
323 | QtWidgets.QMessageBox.Cancel)
324 | return d.exec_()
325 |
326 | def dirtySaveCancel(self):
327 | d = QtWidgets.QMessageBox()
328 | d.setWindowTitle(i18n("Question"))
329 | d.setText(i18n("Document was modify"))
330 | d.setInformativeText(i18n("Save changes?"))
331 | d.setIcon(QtWidgets.QMessageBox.Question)
332 | d.setStandardButtons(QtWidgets.QMessageBox.Save |
333 | QtWidgets.QMessageBox.Cancel)
334 | return d.exec_()
335 |
336 | def fileOpen(self):
337 | name, dummy = QtWidgets.QFileDialog.getOpenFileName(
338 | self, i18n("Open File"), self.cwd,
339 | i18n("Python files (*.py);;All files (*)"))
340 | if name:
341 | code_edit = self.createEditor()
342 | code_edit.file.open(name)
343 | i = self.tabber.add_code_edit(code_edit)
344 | self.tabber.setCurrentIndex(i)
345 | self.cwd = os.path.dirname(name)
346 |
347 | def fileSave(self):
348 | ed = self.tabber.active_editor
349 | if not ed:
350 | return False
351 | is_new = ed.file.path.startswith(i18n('NewFile.py (%d)')
352 | .replace(' (%d)', ''))
353 | if not ed.file.path or is_new:
354 | path, dummy = QtWidgets.QFileDialog. \
355 | getSaveFileName(self, i18n("Save File"), self.cwd,
356 | i18n("Python files (*.py);;All files (*)"))
357 | else:
358 | path = ed.file.path
359 | if not path:
360 | return False
361 | old = self.tabber.currentIndex()
362 | self.tabber.setCurrentWidget(ed)
363 | self.tabber.save_current(path)
364 | self.tabber.setCurrentIndex(old)
365 | return True
366 |
367 | def openTerm(self):
368 | if self.termAction.isChecked():
369 | self.stack.setCurrentIndex(1)
370 | self.termAction.setIcon(icon('terminal-out'))
371 | self.termAction.setText(i18n('To Editor'))
372 | self.term.setFocus()
373 | # self.term.remoteExec(b'\x04')
374 | else:
375 | self.termAction.setIcon(icon('terminal'))
376 | self.termAction.setText(i18n('Terminal'))
377 | self.stack.setCurrentIndex(0)
378 |
379 | def progRun(self):
380 | self._targetExec(self.tabber.active_editor.toPlainText())
381 | self.termAction.setChecked(True)
382 | self.openTerm()
383 |
384 | def _targetExec(self, script, continuation=None):
385 | def progrun2(text):
386 | # print("{} {}".format(4, progrun2.text))
387 | progrun2.text += text
388 | if progrun2.text.endswith(b'\x04>'):
389 | # print("{} {}".format(5, progrun2.text))
390 | if isinstance(continuation, collections.Callable):
391 | continuation(progrun2.text)
392 | return True
393 | return False
394 |
395 | def progrun1(text):
396 | progrun1.text += text
397 | # print("{} {}".format(2, progrun1.text))
398 | if progrun1.text.endswith(b'to exit\r\n>'):
399 | progrun2.text = b''
400 | # print("{} {}".format(3, progrun1.text))
401 | cmd = 'print("\033c")\r{}\r\x04\x02'.format(script)
402 | # print("{} {}".format(3.5, cmd))
403 | self.term.remoteExec(bytes(cmd, 'utf-8'), progrun2)
404 | return True
405 | return False
406 | progrun1.text = b''
407 | # print(1)
408 | self.term.remoteExec(b'\r\x03\x03\r\x01', progrun1)
409 |
410 | def showDir(self):
411 | def finished(raw):
412 | text = ''.join(re.findall(r"(\[.*?\])", raw.decode()))
413 | print((raw, text))
414 | self.onListDir.emit(text)
415 | self._targetExec('print(os.listdir())', finished)
416 |
417 | def _showDir(self, text):
418 | items = eval(text)
419 | d = QtWidgets.QDialog(self)
420 | l = QtWidgets.QVBoxLayout(d)
421 | h = QtWidgets.QListWidget(d)
422 | l.addWidget(h)
423 | h.addItems(items)
424 | h.itemClicked.connect(d.accept)
425 | d.exec_()
426 |
427 | def _writeRemoteFile(self, local_name):
428 | '''upload local file to remote device (target board)'''
429 | def finished(raw):
430 | print(('_writeRemoteFile terminated: ', raw))
431 | name = os.path.basename(local_name)
432 | name, ok = QtWidgets.QInputDialog.getText(self, i18n("Download"),
433 | i18n("Remote Name"),
434 | text=name)
435 | if not ok:
436 | return
437 | remote_name = '/flash/{}'.format(name)
438 | if os.path.exists(local_name):
439 | with open(local_name, 'rb') as f:
440 | data = f.read()
441 | else:
442 | data = self.tabber.active_editor.toPlainText()
443 | cmd = "with open(\"{}\", 'wb') as f:\r" \
444 | " f.write({})".format(remote_name, repr(data))
445 | print(("Writing remote to ", remote_name, repr(data)))
446 | print(("--------------\n", cmd, "\n--------------"))
447 | self._targetExec(cmd, finished)
448 |
449 | def progDownload(self):
450 | self._writeRemoteFile(self.tabber.active_editor.file.path)
451 |
452 | global app
453 |
454 |
455 | def main():
456 | app = QtWidgets.QApplication(sys.argv)
457 | app.setQuitOnLastWindowClosed(True)
458 | splash = QtWidgets.QSplashScreen()
459 | splash.setPixmap(about_pixmap())
460 | splash.show()
461 |
462 | def do_app():
463 | splash.close()
464 | w = MainWindow()
465 | w.show()
466 | QtCore.QTimer.singleShot(2000, do_app)
467 | sys.exit(app.exec_())
468 |
469 | if __name__ == "__main__":
470 | main()
471 |
--------------------------------------------------------------------------------
/svg2png.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | set BASE=%~dp0
3 | set S=%BASE%src\images\
4 | set D=%BASE%share\uPyIDE\images\
5 | set PROG="%ProgramFiles%\Inkscape\inkscape.exe"
6 |
7 | for %%f in (%S%*.svg) DO (
8 | ECHO convert %%f to %D%%%~nf.png
9 | %PROG% -z %%f -e %D%%%~nf.png
10 | )
11 | pause
12 |
--------------------------------------------------------------------------------