Skip to content

Commit 6611bf9

Browse files
committed
Issue: #22 -- Primera implementación del protocolo para Arduino
Agrego la clase base para manejar la comunicación junto con los conectores (solo se implemento comunicación serial y un mock). Falta pullir algunas cosas pero la mayor parte está. Se agrega el controlador para el firmware de Arduino Se agrega test unitario del conector (se articula con el mock)
1 parent 632efd9 commit 6611bf9

File tree

7 files changed

+252
-0
lines changed

7 files changed

+252
-0
lines changed

MLC/arduino/connection/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
from base import BaseConnection
3+
from serialconnection import SerialConnection
4+
from mockconnection import MockConnection
5+
6+
__all__ = ["BaseConnection", "SerialConnection", "MockConnection" ]

MLC/arduino/connection/base.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
class BaseConnection:
3+
'''
4+
Connection base class
5+
6+
Defines method for I/O with an Arduino device
7+
'''
8+
def send(self,data):
9+
""" Sends data to arduino device """
10+
raise Exception("Implement me!")
11+
12+
def recv(self, length):
13+
""" Receive data from the arduino device """
14+
raise Exception("Implement me!")

MLC/arduino/connection/ethernet.py

Whitespace-only changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from base import BaseConnection
2+
3+
class MockConnection(BaseConnection):
4+
""" Connection that save the data sent using "send" method and response
5+
on a "recv" call using a pre-configured response buffer
6+
"""
7+
def __init__(self, responses):
8+
""" response -- bytes stream of responses that will be used in recv method """
9+
self._received = []
10+
self._responses = responses
11+
self._resp_idx = 0
12+
13+
def send(self, data):
14+
""" \"sends\" the data to a inner buffer """
15+
self._received.insert(0,data)
16+
17+
def pop_data(self):
18+
""" Pops a complete data stream received """
19+
return self._received.pop()
20+
21+
def recv(self, length):
22+
"""
23+
Looping receiver (if buffer run out of data, it add to the response bytes from the beginning)
24+
25+
length -- count of bytes to \"receive\"
26+
"""
27+
pos = self._resp_idx
28+
if pos + length > len(self._responses):
29+
self._resp_idx = (length - (len(self._responses)-pos))%len(self._responses)
30+
return self._responses[pos:] + self._responses[0:length - (len(self._responses)-pos)]
31+
32+
self._resp_idx = pos + length
33+
return self._responses[pos:pos+length]
34+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from base import BaseConnection
2+
import serial
3+
4+
5+
class SerialConnection(BaseConnection):
6+
def __init__(self, **args):
7+
if "port" not in args.keys():
8+
raise Exception("Port is mandatory!")
9+
10+
args["baudrate"] = 115200 if not "baudrate" in args.keys() else args["baudrate"]
11+
args["parity"] = serial.PARITY_NONE if not "parity" in args.keys() else args["parity"]
12+
args["stopbits"] = serial.STOPBITS_ONE if not "stopbits" in args.keys() else args["stopbits"]
13+
args["bytesize"] = serial.EIGHTBITS if not "bytesize" in args.keys() else args["bytesize"]
14+
15+
self._connection = serial.Serial(**args);
16+
17+
18+
def send(self, data):
19+
self._connection.write(data)
20+
21+
def recv(self, length):
22+
return self._connection.read(length)

MLC/arduino/protocol.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
2+
_PROTOCOL_CMDS = { "ANALOG_PRECISION": '\x01\x01%s',
3+
"ANALOG_INPUT" : '\x02\x01%s',
4+
"ANALOG_OUTPUT" : '\x03\x01%s',
5+
"PIN_MODE" : '\x04\x02%s%s',
6+
"REPORT_MODE" : '\x05\x03%s%s%s',
7+
"ACK" : '\xFF\x00',
8+
"ACTUATE" : '\xF0%s',
9+
"ACTUATE_REPORT" : '\xF1' }
10+
11+
class ArduinoInterface:
12+
PIN_MOD = {"INPUT":0, "OUTPUT":1} # 0=input 1=output -- wiring_constants.h
13+
REPORT_MOD = {"AVERAGE":0, "BULK":1, "RT":2}
14+
15+
def __init__(self, connection):
16+
self._connection = connection
17+
self._anlg_inputs = []
18+
self._anlg_outputs = []
19+
self._anlg_precition = 10 #Default Arduino analog precition
20+
21+
def set_precition(self, bits):
22+
if bits > 32 or bits < 1:
23+
raise Exception("Precision bits must be between 1 and 32!")
24+
self._connection.send(_PROTOCOL_CMDS["ANALOG_PRECISION"] % chr(bits))
25+
self._anlg_precition = bits
26+
27+
def set_pin_mode(self, port, mode):
28+
if mode not in self.PIN_MOD.keys():
29+
raise Exception("Pind mode error. Unknown mode: %s. Modes availables: %s " % (mode, str(PIN_MOD.keys())))
30+
31+
self._connection.send(_PROTOCOL_CMDS["PIN_MODE"] % (chr(port), chr(self.PIN_MOD[mode])))
32+
33+
def set_report_mode(self, mode, read_count=1, read_delay=0):
34+
if mode not in self.REPORT_MOD.keys():
35+
raise Exception("Report mode error. Unknown value: %s" % mode)
36+
37+
self._connection.send(_PROTOCOL_CMDS["REPORT_MODE"] % (chr(self.REPORT_MOD[mode]), chr(read_count), chr(read_delay)))
38+
39+
40+
def add_analog_input(self, port):
41+
if port in self._anlg_outputs:
42+
raise Exception("Port %s is configured as output!" % port)
43+
44+
if port not in self._anlg_inputs:
45+
self._connection.send(_PROTOCOL_CMDS["ANALOG_INPUT"] % chr(port))
46+
self._anlg_inputs.append(port)
47+
48+
def add_analog_output(self, port):
49+
if port in self._anlg_inputs:
50+
raise Exception("Port %s is configured as input!" % port)
51+
52+
if port not in self._anlg_outputs:
53+
self._connection.send(_PROTOCOL_CMDS["ANALOG_OUTPUT"] % chr(port))
54+
self._anlg_outputs.append(port)
55+
56+
def actuate(self, data):
57+
"""
58+
Actuate over the input port sent as parameters
59+
All the ports must has been configured as output!
60+
61+
arguments:
62+
data -- port & value set to actuate
63+
"""
64+
payload = ""
65+
size = 0
66+
#TODO: Ver como validar puertos digitales
67+
for i in data:
68+
if i[0] not in self._anlg_outputs:
69+
raise Exception("Port %s not configured as output!" % i[0])
70+
payload = "".join([payload, chr(i[0]), chr((i[1] & 0xFF00) >> 8), chr(i[1] & 0x00FF)])
71+
size += 3
72+
73+
self._connection.send("".join([_PROTOCOL_CMDS["ACTUATE"], chr(size), payload]))
74+
response = self._connection.recv(1)
75+
76+
if response == _PROTOCOL_CMDS["ACK"]:
77+
response = self._connection.recv(1)
78+
raise Exception("Actuate error. Code: %s" % response)
79+
80+
if response <> _PROTOCOL_CMDS["ACTUATE_REPORT"]:
81+
raise Exception("Actuate error. Unknown response %s after actuate operation" % response)
82+
83+
length = ord(self._connection.recv(1))
84+
data = self._connection.recv(length)
85+
86+
pos = 0
87+
results = []
88+
while pos < length:
89+
if ord(data[pos]) in self._anlg_inputs:
90+
results.append((data[pos], (ord(data[pos+1]) << 8) + ord(data[pos+2])))
91+
pos = pos + 3
92+
else:
93+
results.append((data[pos], bool(ord(data[pos+1]))))
94+
pos = pos + 2
95+
96+
return results
97+
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import unittest
2+
from MLC.arduino.connection import MockConnection
3+
from MLC.arduino.protocol import ArduinoInterface
4+
5+
ACK = "\xFF\x00"
6+
NACK = "\xFF\x01"
7+
REPORT = "\xF1\x05\x10\x01\x3D\x05\x20"
8+
9+
10+
class TestArduinoInterface(unittest.TestCase):
11+
12+
def setUp(self):
13+
self._connection = MockConnection(ACK)
14+
self._interface = ArduinoInterface(self._connection)
15+
16+
def test_set_precition(self):
17+
self._interface.set_precition(12)
18+
data = self._connection.pop_data()
19+
self.assertEqual("\x01\x01\x0C", data)
20+
with self.assertRaises(Exception):
21+
self._interface.set_precition(33)
22+
23+
def test_set_pin_mode(self):
24+
self._interface.set_pin_mode(1, "INPUT")
25+
data = self._connection.pop_data()
26+
self.assertEqual("\x04\x02\x01\x00", data)
27+
with self.assertRaises(Exception):
28+
self._interface.set_pin_mode(1, "SOMETHING")
29+
30+
def test_report_mode(self):
31+
self._interface.set_report_mode("AVERAGE")
32+
data = self._connection.pop_data()
33+
self.assertEqual("\x05\x03\x00\x01\x00", data)
34+
self._interface.set_report_mode("AVERAGE", read_count=10, read_delay=5)
35+
data = self._connection.pop_data()
36+
self.assertEqual("\x05\x03\x00\x0A\x05", data)
37+
with self.assertRaises(Exception):
38+
self._interface.set_report_mode("SOMETHING")
39+
40+
def test_add_analog_input(self):
41+
self._interface.add_analog_input(60)
42+
data = self._connection.pop_data()
43+
self.assertEqual("\x02\x01\x3C", data)
44+
self._interface.add_analog_input(60)
45+
with self.assertRaises(Exception): # Checks that no data has been sent
46+
data = self._connection.pop_data()
47+
48+
def test_add_analog_output(self):
49+
self._interface.add_analog_output(60)
50+
data = self._connection.pop_data()
51+
self.assertEqual("\x03\x01\x3C", data)
52+
self._interface.add_analog_output(60)
53+
with self.assertRaises(Exception): # Checks that no data has been sent
54+
data = self._connection.pop_data()
55+
56+
def test_error_adding_output_that_is_input(self):
57+
self._interface.add_analog_input(60)
58+
with self.assertRaises(Exception):
59+
self._interface.add_analog_output(60)
60+
61+
def test_actuate_error_response(self):
62+
self._interface.add_analog_output(60)
63+
self._connection.pop_data()
64+
with self.assertRaises(Exception):
65+
self._interface.actuate([(60, 128)])
66+
67+
def test_actuate(self):
68+
self._connection = MockConnection(REPORT)
69+
self._interface = ArduinoInterface(self._connection)
70+
self._interface.add_analog_output(60)
71+
self._interface.add_analog_input(61)
72+
response = self._interface.actuate([(60, 128)])
73+
self.assertEqual(2, len(response))
74+
self.assertEqual("\x10",response[0][0])
75+
self.assertTrue(response[0][1])
76+
self.assertEqual("\x3D",response[1][0])
77+
self.assertEqual(0x0520,response[1][1])
78+
79+

0 commit comments

Comments
 (0)