jacarev3 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jacarev3-0.1.0/LICENSE +21 -0
- jacarev3-0.1.0/PKG-INFO +182 -0
- jacarev3-0.1.0/README.md +153 -0
- jacarev3-0.1.0/pyproject.toml +40 -0
- jacarev3-0.1.0/setup.cfg +4 -0
- jacarev3-0.1.0/src/jacarev3/__init__.py +4 -0
- jacarev3-0.1.0/src/jacarev3/conexao.py +55 -0
- jacarev3-0.1.0/src/jacarev3/protocolo.py +169 -0
- jacarev3-0.1.0/src/jacarev3/robo.py +206 -0
- jacarev3-0.1.0/src/jacarev3.egg-info/PKG-INFO +182 -0
- jacarev3-0.1.0/src/jacarev3.egg-info/SOURCES.txt +11 -0
- jacarev3-0.1.0/src/jacarev3.egg-info/dependency_links.txt +1 -0
- jacarev3-0.1.0/src/jacarev3.egg-info/top_level.txt +1 -0
jacarev3-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ítalo Vinicius Barbosa Felix
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
jacarev3-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jacarev3
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lib em português pra controlar o LEGO EV3 via Bluetooth — protocolo Direct Commands implementado do zero, sem dependências externas
|
|
5
|
+
Author: Ítalo Vinicius Barbosa Felix
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/i-barbosa/jacarEV3
|
|
8
|
+
Project-URL: Repository, https://github.com/i-barbosa/jacarEV3
|
|
9
|
+
Project-URL: Issues, https://github.com/i-barbosa/jacarEV3/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/i-barbosa/jacarEV3/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: ev3,lego,mindstorms,robotica,bluetooth,educacional
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Education
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
21
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
22
|
+
Classifier: Topic :: Education
|
|
23
|
+
Classifier: Topic :: System :: Hardware
|
|
24
|
+
Classifier: Natural Language :: Portuguese (Brazilian)
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# jacarEV3 🐊
|
|
31
|
+
|
|
32
|
+
Lib em português pra controlar o LEGO EV3 via Bluetooth. Sem dependências
|
|
33
|
+
externas — só biblioteca padrão do Python.
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/jacarev3/)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
|
|
38
|
+
## Instalação
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install jacarev3
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Ou pra desenvolver localmente:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone https://github.com/i-barbosa/jacarEV3.git
|
|
48
|
+
cd jacarEV3
|
|
49
|
+
pip install -e . --break-system-packages
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Uso rápido
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from jacarev3 import RoboEV3
|
|
56
|
+
|
|
57
|
+
robo = RoboEV3('00:16:53:64:F8:B8') # MAC do seu EV3
|
|
58
|
+
|
|
59
|
+
robo.apitar()
|
|
60
|
+
robo.led('VERDE')
|
|
61
|
+
|
|
62
|
+
robo.testar_tudo() # testa motores A-D e sensores 1-4
|
|
63
|
+
|
|
64
|
+
robo.fechar()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Ou com `with` (fecha a conexão sozinho):
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
with RoboEV3('00:16:53:64:F8:B8') as robo:
|
|
71
|
+
robo.apitar()
|
|
72
|
+
robo.testar_motores()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Exemplos por funcionalidade
|
|
76
|
+
|
|
77
|
+
### Som
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
robo.apitar() # bip padrão
|
|
81
|
+
robo.apitar(frequencia=880, duracao_ms=300) # agudo e curto
|
|
82
|
+
robo.apitar(volume=5) # mais alto (1-100)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### LED
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
robo.led('VERDE')
|
|
89
|
+
robo.led('VERMELHO_PISCA')
|
|
90
|
+
robo.led('DESLIGADO')
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Motor
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# Gira pra frente 1s, velocidade 30%
|
|
97
|
+
robo.girar_motor('A', velocidade=30, duracao_ms=1000)
|
|
98
|
+
|
|
99
|
+
# Gira pra trás (velocidade negativa)
|
|
100
|
+
robo.girar_motor('A', velocidade=-30, duracao_ms=1000)
|
|
101
|
+
|
|
102
|
+
# Solta o motor sem frear (roda livre)
|
|
103
|
+
robo.parar_motor('A', frear=False)
|
|
104
|
+
|
|
105
|
+
# Testa um motor específico
|
|
106
|
+
robo.testar_motor('B')
|
|
107
|
+
|
|
108
|
+
# Testa A, B, C, D de uma vez (pula os vazios com aviso)
|
|
109
|
+
robo.testar_motores()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Sensores
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
# Cada teste espera o valor MUDAR de verdade antes de seguir
|
|
116
|
+
# (aproxima algo do ultrassônico, aperta o toque, muda a cor)
|
|
117
|
+
robo.testar_ultrassonico(porta=1)
|
|
118
|
+
robo.testar_toque(porta=2)
|
|
119
|
+
robo.testar_cor(porta=3)
|
|
120
|
+
|
|
121
|
+
# Testa só alguns tipos, tempo de espera customizado
|
|
122
|
+
robo.testar_sensores(tipos=('toque', 'cor'), tempo_limite=10)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## API completa
|
|
126
|
+
|
|
127
|
+
### Som e LED
|
|
128
|
+
| Método | Descrição |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `apitar(frequencia=440, duracao_ms=500, volume=1)` | Toca um bip |
|
|
131
|
+
| `led(cor)` | `VERDE`, `VERMELHO`, `AMBAR`, `DESLIGADO`, e variantes `_PISCA` / `_PULSA` |
|
|
132
|
+
|
|
133
|
+
### Motores (portas A-D)
|
|
134
|
+
| Método | Descrição |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `girar_motor(porta, velocidade=30, duracao_ms=1000, frear=True)` | Velocidade de -100 a 100 |
|
|
137
|
+
| `parar_motor(porta, frear=True)` | Para o motor |
|
|
138
|
+
| `testar_motor(porta)` | Testa um motor específico |
|
|
139
|
+
| `testar_motores()` | Testa A, B, C, D de uma vez |
|
|
140
|
+
|
|
141
|
+
### Sensores (portas 1-4)
|
|
142
|
+
| Método | Descrição |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `testar_ultrassonico(porta, tempo_limite=15)` | Espera a distância mudar |
|
|
145
|
+
| `testar_toque(porta, tempo_limite=15)` | Espera pressionado/solto |
|
|
146
|
+
| `testar_cor(porta, tempo_limite=15)` | Espera a cor mudar |
|
|
147
|
+
| `testar_sensores(tipos=(...), tempo_limite=15)` | Testa os 3 tipos nas 4 portas |
|
|
148
|
+
| `testar_tudo(tempo_limite=15)` | Motores + todos os sensores |
|
|
149
|
+
|
|
150
|
+
## Troubleshooting
|
|
151
|
+
|
|
152
|
+
**`OSError` / timeout ao conectar**
|
|
153
|
+
Confirma que o EV3 tá pareado nas Configurações Bluetooth do Windows/Linux
|
|
154
|
+
e que o Bluetooth dele tá visível/ligado antes de rodar o script.
|
|
155
|
+
|
|
156
|
+
**`[AVISO] Sem motor (ou erro) na porta X`**
|
|
157
|
+
Normal se não tiver motor físico conectado nessa porta — o método pula e
|
|
158
|
+
segue pros outros.
|
|
159
|
+
|
|
160
|
+
**`speed must be in range [1-100]`**
|
|
161
|
+
Velocidade tem que ser um valor entre -100 e 100 (direção pelo sinal, não
|
|
162
|
+
por valores fora do range).
|
|
163
|
+
|
|
164
|
+
**`EV3 respondeu com erro pro comando`**
|
|
165
|
+
Geralmente é modo/porta errada no sensor (ex: pedir leitura de cor num
|
|
166
|
+
sensor de toque). Confere se o sensor físico bate com o tipo do método
|
|
167
|
+
chamado.
|
|
168
|
+
|
|
169
|
+
## Status
|
|
170
|
+
|
|
171
|
+
v0.1.0 — protocolo validado byte a byte contra a documentação oficial da
|
|
172
|
+
LEGO (Communication Developer Kit), ainda em bateria de testes no robô
|
|
173
|
+
físico. Veja [CHANGELOG.md](CHANGELOG.md) pro histórico de versões.
|
|
174
|
+
|
|
175
|
+
## Contribuindo
|
|
176
|
+
|
|
177
|
+
Issues e PRs são bem-vindos em
|
|
178
|
+
[github.com/i-barbosa/jacarEV3](https://github.com/i-barbosa/jacarEV3).
|
|
179
|
+
|
|
180
|
+
## Licença
|
|
181
|
+
|
|
182
|
+
MIT — veja [LICENSE](LICENSE).
|
jacarev3-0.1.0/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# jacarEV3 🐊
|
|
2
|
+
|
|
3
|
+
Lib em português pra controlar o LEGO EV3 via Bluetooth. Sem dependências
|
|
4
|
+
externas — só biblioteca padrão do Python.
|
|
5
|
+
|
|
6
|
+
[](https://pypi.org/project/jacarev3/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
## Instalação
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install jacarev3
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Ou pra desenvolver localmente:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/i-barbosa/jacarEV3.git
|
|
19
|
+
cd jacarEV3
|
|
20
|
+
pip install -e . --break-system-packages
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Uso rápido
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from jacarev3 import RoboEV3
|
|
27
|
+
|
|
28
|
+
robo = RoboEV3('00:16:53:64:F8:B8') # MAC do seu EV3
|
|
29
|
+
|
|
30
|
+
robo.apitar()
|
|
31
|
+
robo.led('VERDE')
|
|
32
|
+
|
|
33
|
+
robo.testar_tudo() # testa motores A-D e sensores 1-4
|
|
34
|
+
|
|
35
|
+
robo.fechar()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Ou com `with` (fecha a conexão sozinho):
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
with RoboEV3('00:16:53:64:F8:B8') as robo:
|
|
42
|
+
robo.apitar()
|
|
43
|
+
robo.testar_motores()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Exemplos por funcionalidade
|
|
47
|
+
|
|
48
|
+
### Som
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
robo.apitar() # bip padrão
|
|
52
|
+
robo.apitar(frequencia=880, duracao_ms=300) # agudo e curto
|
|
53
|
+
robo.apitar(volume=5) # mais alto (1-100)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### LED
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
robo.led('VERDE')
|
|
60
|
+
robo.led('VERMELHO_PISCA')
|
|
61
|
+
robo.led('DESLIGADO')
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Motor
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# Gira pra frente 1s, velocidade 30%
|
|
68
|
+
robo.girar_motor('A', velocidade=30, duracao_ms=1000)
|
|
69
|
+
|
|
70
|
+
# Gira pra trás (velocidade negativa)
|
|
71
|
+
robo.girar_motor('A', velocidade=-30, duracao_ms=1000)
|
|
72
|
+
|
|
73
|
+
# Solta o motor sem frear (roda livre)
|
|
74
|
+
robo.parar_motor('A', frear=False)
|
|
75
|
+
|
|
76
|
+
# Testa um motor específico
|
|
77
|
+
robo.testar_motor('B')
|
|
78
|
+
|
|
79
|
+
# Testa A, B, C, D de uma vez (pula os vazios com aviso)
|
|
80
|
+
robo.testar_motores()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Sensores
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# Cada teste espera o valor MUDAR de verdade antes de seguir
|
|
87
|
+
# (aproxima algo do ultrassônico, aperta o toque, muda a cor)
|
|
88
|
+
robo.testar_ultrassonico(porta=1)
|
|
89
|
+
robo.testar_toque(porta=2)
|
|
90
|
+
robo.testar_cor(porta=3)
|
|
91
|
+
|
|
92
|
+
# Testa só alguns tipos, tempo de espera customizado
|
|
93
|
+
robo.testar_sensores(tipos=('toque', 'cor'), tempo_limite=10)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## API completa
|
|
97
|
+
|
|
98
|
+
### Som e LED
|
|
99
|
+
| Método | Descrição |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `apitar(frequencia=440, duracao_ms=500, volume=1)` | Toca um bip |
|
|
102
|
+
| `led(cor)` | `VERDE`, `VERMELHO`, `AMBAR`, `DESLIGADO`, e variantes `_PISCA` / `_PULSA` |
|
|
103
|
+
|
|
104
|
+
### Motores (portas A-D)
|
|
105
|
+
| Método | Descrição |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `girar_motor(porta, velocidade=30, duracao_ms=1000, frear=True)` | Velocidade de -100 a 100 |
|
|
108
|
+
| `parar_motor(porta, frear=True)` | Para o motor |
|
|
109
|
+
| `testar_motor(porta)` | Testa um motor específico |
|
|
110
|
+
| `testar_motores()` | Testa A, B, C, D de uma vez |
|
|
111
|
+
|
|
112
|
+
### Sensores (portas 1-4)
|
|
113
|
+
| Método | Descrição |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `testar_ultrassonico(porta, tempo_limite=15)` | Espera a distância mudar |
|
|
116
|
+
| `testar_toque(porta, tempo_limite=15)` | Espera pressionado/solto |
|
|
117
|
+
| `testar_cor(porta, tempo_limite=15)` | Espera a cor mudar |
|
|
118
|
+
| `testar_sensores(tipos=(...), tempo_limite=15)` | Testa os 3 tipos nas 4 portas |
|
|
119
|
+
| `testar_tudo(tempo_limite=15)` | Motores + todos os sensores |
|
|
120
|
+
|
|
121
|
+
## Troubleshooting
|
|
122
|
+
|
|
123
|
+
**`OSError` / timeout ao conectar**
|
|
124
|
+
Confirma que o EV3 tá pareado nas Configurações Bluetooth do Windows/Linux
|
|
125
|
+
e que o Bluetooth dele tá visível/ligado antes de rodar o script.
|
|
126
|
+
|
|
127
|
+
**`[AVISO] Sem motor (ou erro) na porta X`**
|
|
128
|
+
Normal se não tiver motor físico conectado nessa porta — o método pula e
|
|
129
|
+
segue pros outros.
|
|
130
|
+
|
|
131
|
+
**`speed must be in range [1-100]`**
|
|
132
|
+
Velocidade tem que ser um valor entre -100 e 100 (direção pelo sinal, não
|
|
133
|
+
por valores fora do range).
|
|
134
|
+
|
|
135
|
+
**`EV3 respondeu com erro pro comando`**
|
|
136
|
+
Geralmente é modo/porta errada no sensor (ex: pedir leitura de cor num
|
|
137
|
+
sensor de toque). Confere se o sensor físico bate com o tipo do método
|
|
138
|
+
chamado.
|
|
139
|
+
|
|
140
|
+
## Status
|
|
141
|
+
|
|
142
|
+
v0.1.0 — protocolo validado byte a byte contra a documentação oficial da
|
|
143
|
+
LEGO (Communication Developer Kit), ainda em bateria de testes no robô
|
|
144
|
+
físico. Veja [CHANGELOG.md](CHANGELOG.md) pro histórico de versões.
|
|
145
|
+
|
|
146
|
+
## Contribuindo
|
|
147
|
+
|
|
148
|
+
Issues e PRs são bem-vindos em
|
|
149
|
+
[github.com/i-barbosa/jacarEV3](https://github.com/i-barbosa/jacarEV3).
|
|
150
|
+
|
|
151
|
+
## Licença
|
|
152
|
+
|
|
153
|
+
MIT — veja [LICENSE](LICENSE).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "jacarev3"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Lib em português pra controlar o LEGO EV3 via Bluetooth — protocolo Direct Commands implementado do zero, sem dependências externas"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Ítalo Vinicius Barbosa Felix" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ev3", "lego", "mindstorms", "robotica", "bluetooth", "educacional"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Education",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Operating System :: Microsoft :: Windows",
|
|
26
|
+
"Operating System :: POSIX :: Linux",
|
|
27
|
+
"Topic :: Education",
|
|
28
|
+
"Topic :: System :: Hardware",
|
|
29
|
+
"Natural Language :: Portuguese (Brazilian)",
|
|
30
|
+
]
|
|
31
|
+
dependencies = []
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/i-barbosa/jacarEV3"
|
|
35
|
+
Repository = "https://github.com/i-barbosa/jacarEV3"
|
|
36
|
+
Issues = "https://github.com/i-barbosa/jacarEV3/issues"
|
|
37
|
+
Changelog = "https://github.com/i-barbosa/jacarEV3/blob/main/CHANGELOG.md"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
jacarev3-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
jacarev3.conexao
|
|
3
|
+
--------------
|
|
4
|
+
Transporte de baixo nível: abre um socket Bluetooth clássico (RFCOMM) com o
|
|
5
|
+
EV3 e manda/recebe os pacotes crus montados pelo módulo `protocolo`.
|
|
6
|
+
|
|
7
|
+
Usa só a biblioteca padrão do Python (`socket` com AF_BLUETOOTH), sem
|
|
8
|
+
dependência externa — funciona no Windows (10+) e no Linux com BlueZ.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import socket
|
|
12
|
+
import itertools
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ConexaoBluetooth:
|
|
16
|
+
"""Conexão RFCOMM clássica com o EV3 (a mesma usada pelo app oficial)."""
|
|
17
|
+
|
|
18
|
+
CANAL_RFCOMM_PADRAO = 1
|
|
19
|
+
|
|
20
|
+
def __init__(self, mac, canal=None, timeout=10):
|
|
21
|
+
self.mac = mac
|
|
22
|
+
self.canal = canal or self.CANAL_RFCOMM_PADRAO
|
|
23
|
+
self._contador = itertools.count(1)
|
|
24
|
+
self.socket = socket.socket(
|
|
25
|
+
socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM
|
|
26
|
+
)
|
|
27
|
+
self.socket.settimeout(timeout)
|
|
28
|
+
self.socket.connect((self.mac, self.canal))
|
|
29
|
+
|
|
30
|
+
def proximo_contador(self):
|
|
31
|
+
return next(self._contador) & 0xFFFF
|
|
32
|
+
|
|
33
|
+
def enviar(self, pacote):
|
|
34
|
+
self.socket.send(pacote)
|
|
35
|
+
|
|
36
|
+
def receber(self, tamanho_max=1024):
|
|
37
|
+
return self.socket.recv(tamanho_max)
|
|
38
|
+
|
|
39
|
+
def fechar(self):
|
|
40
|
+
try:
|
|
41
|
+
self.socket.close()
|
|
42
|
+
except OSError:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
def __enter__(self):
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
def __exit__(self, *exc):
|
|
49
|
+
self.fechar()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Espaço reservado pra outros transportes (WiFi/USB) no futuro:
|
|
53
|
+
# ConexaoWiFi -> socket TCP na porta 5555 + handshake de "unlock"
|
|
54
|
+
# ConexaoUSB -> via pyusb, endpoints bulk do EV3 (vendor/product id
|
|
55
|
+
# conhecidos: 0x0694 / 0x0005)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
jacarev3.protocolo
|
|
3
|
+
-----------------
|
|
4
|
+
Implementação própria do protocolo "Direct Commands" do EV3, escrita do zero
|
|
5
|
+
a partir de duas fontes públicas e oficiais da LEGO:
|
|
6
|
+
|
|
7
|
+
1. "LEGO MINDSTORMS EV3 Communication Developer Kit" (PDF oficial da LEGO,
|
|
8
|
+
documenta o formato dos pacotes e dá exemplos de bytes reais).
|
|
9
|
+
2. bytecodes.h — cabeçalho oficial do firmware EV3, publicado pela LEGO em
|
|
10
|
+
github.com/mindboards/ev3sources (lista os opcodes da VM).
|
|
11
|
+
|
|
12
|
+
Não copia código de nenhuma lib de terceiro (como o ev3_dc, que é GPLv3) —
|
|
13
|
+
só reimplementa o protocolo público documentado pela própria fabricante,
|
|
14
|
+
do mesmo jeito que dezenas de outras libs independentes (ev3dev-lang-python,
|
|
15
|
+
Rust ev3-dc, etc.) já fizeram, cada uma com sua própria licença.
|
|
16
|
+
|
|
17
|
+
Formato de um Direct Command (confirmado byte a byte no PDF oficial):
|
|
18
|
+
byte 0-1: tamanho do comando (little endian, sem contar esses 2 bytes)
|
|
19
|
+
byte 2-3: contador de mensagem (little endian)
|
|
20
|
+
byte 4: tipo (0x00 = com resposta, 0x80 = sem resposta)
|
|
21
|
+
byte 5-6: alocação de variáveis globais/locais (empacotado)
|
|
22
|
+
byte 7+: bytecode do comando
|
|
23
|
+
|
|
24
|
+
Resposta:
|
|
25
|
+
byte 0-1: tamanho (LE)
|
|
26
|
+
byte 2-3: contador (LE, igual ao do comando)
|
|
27
|
+
byte 4: tipo (0x02 = OK, 0x04 = erro)
|
|
28
|
+
byte 5+: buffer de variáveis globais (o que você pediu pra devolver)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import struct
|
|
32
|
+
|
|
33
|
+
DIRECT_COMMAND_REPLY = 0x00
|
|
34
|
+
DIRECT_COMMAND_NO_REPLY = 0x80
|
|
35
|
+
DIRECT_REPLY_OK = 0x02
|
|
36
|
+
DIRECT_REPLY_ERROR = 0x04
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ---------- Codificação de parâmetros (primitivos LC/GV) ----------
|
|
40
|
+
# Confirmado no PDF oficial: LC0 = valor direto (1 byte), LC1 = prefixo 0x81 +
|
|
41
|
+
# 1 byte, LC2 = prefixo 0x82 + 2 bytes LE, LC4 = prefixo 0x83 + 4 bytes LE,
|
|
42
|
+
# LCS = prefixo 0x84 + string terminada em zero.
|
|
43
|
+
# GV0 (variável global curta) = 0x60 | índice — confirmado batendo com os
|
|
44
|
+
# bytes de exemplo do PDF (leitura de sensor devolvendo em GV0(0) = byte 0x60).
|
|
45
|
+
|
|
46
|
+
def lc0(v):
|
|
47
|
+
return bytes([v & 0x3F])
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def lc1(v):
|
|
51
|
+
return bytes([0x81]) + struct.pack('<b', v)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def lc2(v):
|
|
55
|
+
return bytes([0x82]) + struct.pack('<h', v)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def lc4(v):
|
|
59
|
+
return bytes([0x83]) + struct.pack('<i', v)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def lcs(texto):
|
|
63
|
+
return bytes([0x84]) + texto.encode('ascii') + b'\x00'
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def gv0(indice):
|
|
67
|
+
return bytes([0x60 | (indice & 0x1F)])
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def lc_auto(v):
|
|
71
|
+
"""Escolhe automaticamente o menor encoding que cabe o valor."""
|
|
72
|
+
if -32 <= v <= 31:
|
|
73
|
+
return lc0(v)
|
|
74
|
+
if -128 <= v <= 127:
|
|
75
|
+
return lc1(v)
|
|
76
|
+
if -32768 <= v <= 32767:
|
|
77
|
+
return lc2(v)
|
|
78
|
+
return lc4(v)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ---------- Opcodes (de bytecodes.h, header oficial da LEGO) ----------
|
|
82
|
+
|
|
83
|
+
opSOUND = 0x94
|
|
84
|
+
opUI_WRITE = 0x82
|
|
85
|
+
opINPUT_DEVICE = 0x99
|
|
86
|
+
opOUTPUT_STOP = 0xA3
|
|
87
|
+
opOUTPUT_STEP_SPEED = 0xAE
|
|
88
|
+
opOUTPUT_TIME_SPEED = 0xAF
|
|
89
|
+
|
|
90
|
+
# Sub-códigos de opSOUND
|
|
91
|
+
SOUND_BREAK = 0
|
|
92
|
+
SOUND_TONE = 1
|
|
93
|
+
|
|
94
|
+
# Sub-código de opUI_WRITE
|
|
95
|
+
UI_WRITE_LED = 27
|
|
96
|
+
|
|
97
|
+
# Sub-códigos de opINPUT_DEVICE
|
|
98
|
+
INPUT_READY_RAW = 28
|
|
99
|
+
INPUT_READY_SI = 29
|
|
100
|
+
INPUT_CLR_CHANGES = 26
|
|
101
|
+
|
|
102
|
+
# Portas de motor (bitmask, confirmado nos bytes de exemplo do PDF)
|
|
103
|
+
PORTA_A = 0x01
|
|
104
|
+
PORTA_B = 0x02
|
|
105
|
+
PORTA_C = 0x04
|
|
106
|
+
PORTA_D = 0x08
|
|
107
|
+
|
|
108
|
+
# Ação de parada de motor
|
|
109
|
+
PARAR_COAST = 0
|
|
110
|
+
PARAR_BRAKE = 1
|
|
111
|
+
|
|
112
|
+
# Cores de LED (0-9, da UI_WRITE_SUBCODE)
|
|
113
|
+
LED_PRETO = 0
|
|
114
|
+
LED_VERDE = 1
|
|
115
|
+
LED_VERMELHO = 2
|
|
116
|
+
LED_AMBAR = 3
|
|
117
|
+
LED_VERDE_PISCA = 4
|
|
118
|
+
LED_VERMELHO_PISCA = 5
|
|
119
|
+
LED_AMBAR_PISCA = 6
|
|
120
|
+
LED_VERDE_PULSA = 7
|
|
121
|
+
LED_VERMELHO_PULSA = 8
|
|
122
|
+
LED_AMBAR_PULSA = 9
|
|
123
|
+
|
|
124
|
+
# Modos de sensor mais usados (públicos, mesmos valores usados por
|
|
125
|
+
# ev3dev/ev3-g e outras implementações independentes)
|
|
126
|
+
MODO_TOQUE = 0 # 0 = solto, 1 = pressionado
|
|
127
|
+
MODO_ULTRASSONICO_CM = 0 # distância em cm
|
|
128
|
+
MODO_COR_REFLETIDA = 0
|
|
129
|
+
MODO_COR_AMBIENTE = 1
|
|
130
|
+
MODO_COR_COR = 2 # 0-7: nenhuma, preto, azul, verde, amarelo, vermelho, branco, marrom
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class Comando:
|
|
134
|
+
"""Monta um Direct Command byte a byte."""
|
|
135
|
+
|
|
136
|
+
def __init__(self):
|
|
137
|
+
self.bytecode = b''
|
|
138
|
+
|
|
139
|
+
def add(self, *partes):
|
|
140
|
+
for p in partes:
|
|
141
|
+
self.bytecode += p if isinstance(p, (bytes, bytearray)) else bytes([p])
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
def montar(self, contador, com_resposta=False, bytes_globais=0):
|
|
145
|
+
tipo = DIRECT_COMMAND_REPLY if com_resposta else DIRECT_COMMAND_NO_REPLY
|
|
146
|
+
# aloca variáveis globais: 10 bits, byte5 = bits baixos, byte6 bits 0-1 = bits altos
|
|
147
|
+
header_vars = bytes([
|
|
148
|
+
bytes_globais & 0xFF,
|
|
149
|
+
(bytes_globais >> 8) & 0x03,
|
|
150
|
+
])
|
|
151
|
+
corpo = bytes([tipo]) + header_vars + self.bytecode
|
|
152
|
+
# "tamanho" no protocolo EV3 conta o contador (2 bytes) + corpo,
|
|
153
|
+
# excluindo só os 2 bytes do próprio campo de tamanho.
|
|
154
|
+
tamanho = 2 + len(corpo)
|
|
155
|
+
return struct.pack('<HH', tamanho, contador) + corpo
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def parse_resposta(dados):
|
|
159
|
+
"""Retorna (contador, ok, payload) de uma resposta do EV3."""
|
|
160
|
+
if len(dados) < 5:
|
|
161
|
+
raise ValueError("Resposta curta demais")
|
|
162
|
+
tamanho, contador, tipo = struct.unpack('<HHB', dados[:5])
|
|
163
|
+
ok = (tipo == DIRECT_REPLY_OK)
|
|
164
|
+
payload = dados[5:5 + tamanho - 3]
|
|
165
|
+
return contador, ok, payload
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def ler_float(payload, offset=0):
|
|
169
|
+
return struct.unpack_from('<f', payload, offset)[0]
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
jacarev3.robo
|
|
3
|
+
-----------
|
|
4
|
+
RoboEV3 — mesma API de antes (apitar, led, testar_motores, testar_sensores...)
|
|
5
|
+
mas agora rodando 100% no protocolo próprio (protocolo.py + conexao.py),
|
|
6
|
+
sem depender do pacote `ev3_dc` (GPLv3).
|
|
7
|
+
|
|
8
|
+
AVISO: primeira versão escrita do zero — os opcodes vêm da documentação
|
|
9
|
+
oficial da LEGO, mas ainda não foi validada em bateria extensa no robô
|
|
10
|
+
físico. Testa com calma e reporta qualquer comportamento estranho.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import time
|
|
14
|
+
import struct
|
|
15
|
+
|
|
16
|
+
from . import protocolo as p
|
|
17
|
+
from .conexao import ConexaoBluetooth
|
|
18
|
+
|
|
19
|
+
CORES_SENSOR = {
|
|
20
|
+
0: 'nenhuma', 1: 'preto', 2: 'azul', 3: 'verde', 4: 'amarelo',
|
|
21
|
+
5: 'vermelho', 6: 'branco', 7: 'marrom',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
CODIGOS_LED = {
|
|
25
|
+
'PRETO': p.LED_PRETO, 'DESLIGADO': p.LED_PRETO, 'OFF': p.LED_PRETO,
|
|
26
|
+
'VERDE': p.LED_VERDE,
|
|
27
|
+
'VERMELHO': p.LED_VERMELHO,
|
|
28
|
+
'AMBAR': p.LED_AMBAR, 'LARANJA': p.LED_AMBAR,
|
|
29
|
+
'VERDE_PISCA': p.LED_VERDE_PISCA,
|
|
30
|
+
'VERMELHO_PISCA': p.LED_VERMELHO_PISCA,
|
|
31
|
+
'AMBAR_PISCA': p.LED_AMBAR_PISCA,
|
|
32
|
+
'VERDE_PULSA': p.LED_VERDE_PULSA,
|
|
33
|
+
'VERMELHO_PULSA': p.LED_VERMELHO_PULSA,
|
|
34
|
+
'AMBAR_PULSA': p.LED_AMBAR_PULSA,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
PORTAS_MOTOR = {'A': p.PORTA_A, 'B': p.PORTA_B, 'C': p.PORTA_C, 'D': p.PORTA_D}
|
|
38
|
+
PORTAS_SENSOR = {1: 0, 2: 1, 3: 2, 4: 3} # porta física -> índice interno (0-based)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class RoboEV3:
|
|
42
|
+
def __init__(self, mac, canal=None, timeout=10):
|
|
43
|
+
self.conexao = ConexaoBluetooth(mac, canal=canal, timeout=timeout)
|
|
44
|
+
|
|
45
|
+
def fechar(self):
|
|
46
|
+
self.conexao.fechar()
|
|
47
|
+
|
|
48
|
+
def __enter__(self):
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def __exit__(self, *exc):
|
|
52
|
+
self.fechar()
|
|
53
|
+
|
|
54
|
+
# ---------- envio interno ----------
|
|
55
|
+
|
|
56
|
+
def _enviar(self, comando, com_resposta=False, bytes_globais=0):
|
|
57
|
+
contador = self.conexao.proximo_contador()
|
|
58
|
+
pacote = comando.montar(contador, com_resposta=com_resposta, bytes_globais=bytes_globais)
|
|
59
|
+
self.conexao.enviar(pacote)
|
|
60
|
+
if com_resposta:
|
|
61
|
+
dados = self.conexao.receber()
|
|
62
|
+
_, ok, payload = p.parse_resposta(dados)
|
|
63
|
+
if not ok:
|
|
64
|
+
raise RuntimeError("EV3 respondeu com erro pro comando")
|
|
65
|
+
return payload
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
# ---------- Som / LED ----------
|
|
69
|
+
|
|
70
|
+
def apitar(self, frequencia=440, duracao_ms=500, volume=1):
|
|
71
|
+
cmd = p.Comando().add(
|
|
72
|
+
p.opSOUND, p.SOUND_TONE,
|
|
73
|
+
p.lc_auto(volume), p.lc_auto(frequencia), p.lc_auto(duracao_ms),
|
|
74
|
+
)
|
|
75
|
+
self._enviar(cmd)
|
|
76
|
+
|
|
77
|
+
def led(self, cor):
|
|
78
|
+
codigo = CODIGOS_LED.get(cor.upper())
|
|
79
|
+
if codigo is None:
|
|
80
|
+
raise ValueError(f"Cor de LED desconhecida: {cor}. Opções: {list(CODIGOS_LED)}")
|
|
81
|
+
cmd = p.Comando().add(p.opUI_WRITE, p.UI_WRITE_LED, p.lc0(codigo))
|
|
82
|
+
self._enviar(cmd)
|
|
83
|
+
|
|
84
|
+
# ---------- Espera por mudança (igual antes) ----------
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def espera_mudar(ler_valor, tempo_limite=15, intervalo=0.2, formatar=str, rotulo=""):
|
|
88
|
+
inicial = ler_valor()
|
|
89
|
+
print(f" Valor inicial{f' ({rotulo})' if rotulo else ''}: {formatar(inicial)}")
|
|
90
|
+
inicio = time.time()
|
|
91
|
+
while time.time() - inicio < tempo_limite:
|
|
92
|
+
atual = ler_valor()
|
|
93
|
+
if atual != inicial:
|
|
94
|
+
print(f" Mudou! {formatar(inicial)} -> {formatar(atual)}")
|
|
95
|
+
return True
|
|
96
|
+
time.sleep(intervalo)
|
|
97
|
+
print(f" [TIMEOUT] Nenhuma mudança em {tempo_limite}s.")
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
# ---------- Motores ----------
|
|
101
|
+
|
|
102
|
+
def girar_motor(self, porta, velocidade=30, duracao_ms=1000, frear=True):
|
|
103
|
+
"""
|
|
104
|
+
Gira o motor por tempo (mais simples e robusto que graus/posição).
|
|
105
|
+
velocidade: -100 a 100 (negativo = sentido contrário)
|
|
106
|
+
"""
|
|
107
|
+
bit_porta = PORTAS_MOTOR[porta]
|
|
108
|
+
acao = p.PARAR_BRAKE if frear else p.PARAR_COAST
|
|
109
|
+
cmd = p.Comando().add(
|
|
110
|
+
p.opOUTPUT_TIME_SPEED,
|
|
111
|
+
p.lc0(0), # layer 0
|
|
112
|
+
p.lc0(bit_porta), # porta
|
|
113
|
+
p.lc1(velocidade), # velocidade
|
|
114
|
+
p.lc0(0), # step1 (ramp-up) = 0
|
|
115
|
+
p.lc_auto(duracao_ms), # step2 (duração em ms)
|
|
116
|
+
p.lc0(0), # step3 (ramp-down) = 0
|
|
117
|
+
p.lc0(acao), # ação ao parar
|
|
118
|
+
)
|
|
119
|
+
self._enviar(cmd)
|
|
120
|
+
time.sleep(duracao_ms / 1000 + 0.1)
|
|
121
|
+
|
|
122
|
+
def parar_motor(self, porta, frear=True):
|
|
123
|
+
bit_porta = PORTAS_MOTOR[porta]
|
|
124
|
+
acao = p.PARAR_BRAKE if frear else p.PARAR_COAST
|
|
125
|
+
cmd = p.Comando().add(p.opOUTPUT_STOP, p.lc0(0), p.lc0(bit_porta), p.lc0(acao))
|
|
126
|
+
self._enviar(cmd)
|
|
127
|
+
|
|
128
|
+
def testar_motor(self, porta, velocidade=30, duracao_ms=800):
|
|
129
|
+
print(f"Girando motor {porta} pra frente...")
|
|
130
|
+
self.girar_motor(porta, velocidade=velocidade, duracao_ms=duracao_ms)
|
|
131
|
+
time.sleep(0.2)
|
|
132
|
+
print(f"Girando motor {porta} pra trás...")
|
|
133
|
+
self.girar_motor(porta, velocidade=-velocidade, duracao_ms=duracao_ms)
|
|
134
|
+
|
|
135
|
+
def testar_motores(self):
|
|
136
|
+
print("========== TESTANDO MOTORES ==========\n")
|
|
137
|
+
for letra in PORTAS_MOTOR:
|
|
138
|
+
print(f"--- Motor na porta {letra} ---")
|
|
139
|
+
try:
|
|
140
|
+
self.testar_motor(letra)
|
|
141
|
+
print(f"Motor {letra} OK!\n")
|
|
142
|
+
except Exception as e:
|
|
143
|
+
print(f"[AVISO] Sem motor (ou erro) na porta {letra}: {e}\n")
|
|
144
|
+
|
|
145
|
+
# ---------- Sensores ----------
|
|
146
|
+
|
|
147
|
+
def _ler_sensor(self, indice_porta, modo, n_valores=1):
|
|
148
|
+
cmd = p.Comando().add(
|
|
149
|
+
p.opINPUT_DEVICE, p.INPUT_READY_SI,
|
|
150
|
+
p.lc0(0), # layer 0
|
|
151
|
+
p.lc0(indice_porta), # porta (0-3)
|
|
152
|
+
p.lc0(0), # DO_NOT_CHANGE_TYPE
|
|
153
|
+
p.lc0(modo), # modo
|
|
154
|
+
p.lc0(n_valores), # quantidade de valores
|
|
155
|
+
p.gv0(0), # onde guardar a resposta
|
|
156
|
+
)
|
|
157
|
+
payload = self._enviar(cmd, com_resposta=True, bytes_globais=4 * n_valores)
|
|
158
|
+
if n_valores == 1:
|
|
159
|
+
return struct.unpack_from('<f', payload, 0)[0]
|
|
160
|
+
return struct.unpack_from(f'<{n_valores}f', payload, 0)
|
|
161
|
+
|
|
162
|
+
def testar_ultrassonico(self, porta, tempo_limite=15):
|
|
163
|
+
indice = PORTAS_SENSOR[porta]
|
|
164
|
+
self.espera_mudar(
|
|
165
|
+
lambda: round(self._ler_sensor(indice, p.MODO_ULTRASSONICO_CM), 1),
|
|
166
|
+
tempo_limite=tempo_limite,
|
|
167
|
+
formatar=lambda v: f"{v} cm",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def testar_toque(self, porta, tempo_limite=15):
|
|
171
|
+
indice = PORTAS_SENSOR[porta]
|
|
172
|
+
self.espera_mudar(
|
|
173
|
+
lambda: self._ler_sensor(indice, p.MODO_TOQUE) > 0.5,
|
|
174
|
+
tempo_limite=tempo_limite,
|
|
175
|
+
formatar=lambda v: "pressionado" if v else "solto",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def testar_cor(self, porta, tempo_limite=15):
|
|
179
|
+
indice = PORTAS_SENSOR[porta]
|
|
180
|
+
self.espera_mudar(
|
|
181
|
+
lambda: int(round(self._ler_sensor(indice, p.MODO_COR_COR))),
|
|
182
|
+
tempo_limite=tempo_limite,
|
|
183
|
+
formatar=lambda v: CORES_SENSOR.get(v, f"desconhecida ({v})"),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def testar_sensores(self, tipos=('ultrassonico', 'toque', 'cor'), tempo_limite=15):
|
|
187
|
+
metodos = {
|
|
188
|
+
'ultrassonico': ('SENSORES ULTRASSÔNICOS', self.testar_ultrassonico),
|
|
189
|
+
'toque': ('SENSORES DE TOQUE', self.testar_toque),
|
|
190
|
+
'cor': ('SENSORES DE COR', self.testar_cor),
|
|
191
|
+
}
|
|
192
|
+
for tipo in tipos:
|
|
193
|
+
titulo, metodo = metodos[tipo]
|
|
194
|
+
print(f"========== TESTANDO {titulo} ==========\n")
|
|
195
|
+
for numero in PORTAS_SENSOR:
|
|
196
|
+
print(f"--- {tipo} na porta {numero} ---")
|
|
197
|
+
try:
|
|
198
|
+
metodo(numero, tempo_limite=tempo_limite)
|
|
199
|
+
print()
|
|
200
|
+
except Exception as e:
|
|
201
|
+
print(f"[AVISO] Sem sensor (ou erro) na porta {numero}: {e}\n")
|
|
202
|
+
|
|
203
|
+
def testar_tudo(self, tempo_limite=15):
|
|
204
|
+
self.testar_motores()
|
|
205
|
+
self.testar_sensores(tempo_limite=tempo_limite)
|
|
206
|
+
print("Teste completo finalizado.")
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jacarev3
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lib em português pra controlar o LEGO EV3 via Bluetooth — protocolo Direct Commands implementado do zero, sem dependências externas
|
|
5
|
+
Author: Ítalo Vinicius Barbosa Felix
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/i-barbosa/jacarEV3
|
|
8
|
+
Project-URL: Repository, https://github.com/i-barbosa/jacarEV3
|
|
9
|
+
Project-URL: Issues, https://github.com/i-barbosa/jacarEV3/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/i-barbosa/jacarEV3/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: ev3,lego,mindstorms,robotica,bluetooth,educacional
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Education
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
21
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
22
|
+
Classifier: Topic :: Education
|
|
23
|
+
Classifier: Topic :: System :: Hardware
|
|
24
|
+
Classifier: Natural Language :: Portuguese (Brazilian)
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# jacarEV3 🐊
|
|
31
|
+
|
|
32
|
+
Lib em português pra controlar o LEGO EV3 via Bluetooth. Sem dependências
|
|
33
|
+
externas — só biblioteca padrão do Python.
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/jacarev3/)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
|
|
38
|
+
## Instalação
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install jacarev3
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Ou pra desenvolver localmente:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone https://github.com/i-barbosa/jacarEV3.git
|
|
48
|
+
cd jacarEV3
|
|
49
|
+
pip install -e . --break-system-packages
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Uso rápido
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from jacarev3 import RoboEV3
|
|
56
|
+
|
|
57
|
+
robo = RoboEV3('00:16:53:64:F8:B8') # MAC do seu EV3
|
|
58
|
+
|
|
59
|
+
robo.apitar()
|
|
60
|
+
robo.led('VERDE')
|
|
61
|
+
|
|
62
|
+
robo.testar_tudo() # testa motores A-D e sensores 1-4
|
|
63
|
+
|
|
64
|
+
robo.fechar()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Ou com `with` (fecha a conexão sozinho):
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
with RoboEV3('00:16:53:64:F8:B8') as robo:
|
|
71
|
+
robo.apitar()
|
|
72
|
+
robo.testar_motores()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Exemplos por funcionalidade
|
|
76
|
+
|
|
77
|
+
### Som
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
robo.apitar() # bip padrão
|
|
81
|
+
robo.apitar(frequencia=880, duracao_ms=300) # agudo e curto
|
|
82
|
+
robo.apitar(volume=5) # mais alto (1-100)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### LED
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
robo.led('VERDE')
|
|
89
|
+
robo.led('VERMELHO_PISCA')
|
|
90
|
+
robo.led('DESLIGADO')
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Motor
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# Gira pra frente 1s, velocidade 30%
|
|
97
|
+
robo.girar_motor('A', velocidade=30, duracao_ms=1000)
|
|
98
|
+
|
|
99
|
+
# Gira pra trás (velocidade negativa)
|
|
100
|
+
robo.girar_motor('A', velocidade=-30, duracao_ms=1000)
|
|
101
|
+
|
|
102
|
+
# Solta o motor sem frear (roda livre)
|
|
103
|
+
robo.parar_motor('A', frear=False)
|
|
104
|
+
|
|
105
|
+
# Testa um motor específico
|
|
106
|
+
robo.testar_motor('B')
|
|
107
|
+
|
|
108
|
+
# Testa A, B, C, D de uma vez (pula os vazios com aviso)
|
|
109
|
+
robo.testar_motores()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Sensores
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
# Cada teste espera o valor MUDAR de verdade antes de seguir
|
|
116
|
+
# (aproxima algo do ultrassônico, aperta o toque, muda a cor)
|
|
117
|
+
robo.testar_ultrassonico(porta=1)
|
|
118
|
+
robo.testar_toque(porta=2)
|
|
119
|
+
robo.testar_cor(porta=3)
|
|
120
|
+
|
|
121
|
+
# Testa só alguns tipos, tempo de espera customizado
|
|
122
|
+
robo.testar_sensores(tipos=('toque', 'cor'), tempo_limite=10)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## API completa
|
|
126
|
+
|
|
127
|
+
### Som e LED
|
|
128
|
+
| Método | Descrição |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `apitar(frequencia=440, duracao_ms=500, volume=1)` | Toca um bip |
|
|
131
|
+
| `led(cor)` | `VERDE`, `VERMELHO`, `AMBAR`, `DESLIGADO`, e variantes `_PISCA` / `_PULSA` |
|
|
132
|
+
|
|
133
|
+
### Motores (portas A-D)
|
|
134
|
+
| Método | Descrição |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `girar_motor(porta, velocidade=30, duracao_ms=1000, frear=True)` | Velocidade de -100 a 100 |
|
|
137
|
+
| `parar_motor(porta, frear=True)` | Para o motor |
|
|
138
|
+
| `testar_motor(porta)` | Testa um motor específico |
|
|
139
|
+
| `testar_motores()` | Testa A, B, C, D de uma vez |
|
|
140
|
+
|
|
141
|
+
### Sensores (portas 1-4)
|
|
142
|
+
| Método | Descrição |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `testar_ultrassonico(porta, tempo_limite=15)` | Espera a distância mudar |
|
|
145
|
+
| `testar_toque(porta, tempo_limite=15)` | Espera pressionado/solto |
|
|
146
|
+
| `testar_cor(porta, tempo_limite=15)` | Espera a cor mudar |
|
|
147
|
+
| `testar_sensores(tipos=(...), tempo_limite=15)` | Testa os 3 tipos nas 4 portas |
|
|
148
|
+
| `testar_tudo(tempo_limite=15)` | Motores + todos os sensores |
|
|
149
|
+
|
|
150
|
+
## Troubleshooting
|
|
151
|
+
|
|
152
|
+
**`OSError` / timeout ao conectar**
|
|
153
|
+
Confirma que o EV3 tá pareado nas Configurações Bluetooth do Windows/Linux
|
|
154
|
+
e que o Bluetooth dele tá visível/ligado antes de rodar o script.
|
|
155
|
+
|
|
156
|
+
**`[AVISO] Sem motor (ou erro) na porta X`**
|
|
157
|
+
Normal se não tiver motor físico conectado nessa porta — o método pula e
|
|
158
|
+
segue pros outros.
|
|
159
|
+
|
|
160
|
+
**`speed must be in range [1-100]`**
|
|
161
|
+
Velocidade tem que ser um valor entre -100 e 100 (direção pelo sinal, não
|
|
162
|
+
por valores fora do range).
|
|
163
|
+
|
|
164
|
+
**`EV3 respondeu com erro pro comando`**
|
|
165
|
+
Geralmente é modo/porta errada no sensor (ex: pedir leitura de cor num
|
|
166
|
+
sensor de toque). Confere se o sensor físico bate com o tipo do método
|
|
167
|
+
chamado.
|
|
168
|
+
|
|
169
|
+
## Status
|
|
170
|
+
|
|
171
|
+
v0.1.0 — protocolo validado byte a byte contra a documentação oficial da
|
|
172
|
+
LEGO (Communication Developer Kit), ainda em bateria de testes no robô
|
|
173
|
+
físico. Veja [CHANGELOG.md](CHANGELOG.md) pro histórico de versões.
|
|
174
|
+
|
|
175
|
+
## Contribuindo
|
|
176
|
+
|
|
177
|
+
Issues e PRs são bem-vindos em
|
|
178
|
+
[github.com/i-barbosa/jacarEV3](https://github.com/i-barbosa/jacarEV3).
|
|
179
|
+
|
|
180
|
+
## Licença
|
|
181
|
+
|
|
182
|
+
MIT — veja [LICENSE](LICENSE).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/jacarev3/__init__.py
|
|
5
|
+
src/jacarev3/conexao.py
|
|
6
|
+
src/jacarev3/protocolo.py
|
|
7
|
+
src/jacarev3/robo.py
|
|
8
|
+
src/jacarev3.egg-info/PKG-INFO
|
|
9
|
+
src/jacarev3.egg-info/SOURCES.txt
|
|
10
|
+
src/jacarev3.egg-info/dependency_links.txt
|
|
11
|
+
src/jacarev3.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jacarev3
|