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 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.
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/jacarev3)](https://pypi.org/project/jacarev3/)
36
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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,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
+ [![PyPI](https://img.shields.io/pypi/v/jacarev3)](https://pypi.org/project/jacarev3/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from .robo import RoboEV3
2
+
3
+ __all__ = ["RoboEV3"]
4
+ __version__ = "0.1.0"
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/jacarev3)](https://pypi.org/project/jacarev3/)
36
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ jacarev3