Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 12 additions & 22 deletions mpf/platforms/base_serial_communicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Optional

import asyncio
from serial import SerialException, EIGHTBITS, PARITY_NONE, STOPBITS_ONE
from serial import EIGHTBITS, PARITY_NONE, STOPBITS_ONE

from mpf.core.utility_functions import Util

Expand Down Expand Up @@ -48,22 +48,11 @@ async def connect(self):

async def _connect_to_hardware(self, port, baud, xonxoff=False):
self.log.info("Connecting to %s at %sbps", port, baud)
while True:
try:
connector = self.machine.clock.open_serial_connection(
url=port, baudrate=baud, limit=0, xonxoff=xonxoff,
bytesize=EIGHTBITS, parity=PARITY_NONE, stopbits=STOPBITS_ONE)
self.reader, self.writer = await connector
except SerialException:
if not self.machine.options["production"]:
raise

# if we are in production mode retry
await asyncio.sleep(.1)
self.log.debug("Connection to %s failed. Will retry.", port)
else:
# we got a connection
break
connector = self.machine.clock.open_serial_connection(
url=port, baudrate=baud, limit=0, xonxoff=xonxoff,
bytesize=EIGHTBITS, parity=PARITY_NONE, stopbits=STOPBITS_ONE)

self.reader, self.writer = await connector

serial = self.writer.transport.serial
if hasattr(serial, "set_low_latency_mode"):
Expand Down Expand Up @@ -118,14 +107,15 @@ async def read(self, n=-1):
except asyncio.CancelledError: # pylint: disable-msg=try-except-raise
raise
except Exception as e: # pylint: disable-msg=broad-except
self.log.warning("Serial error: {}".format(e))
self.log.warning("Serial {} error: {}".format(self.port, e))
self.machine.events.post("serial_error")
return None
resp = None

# we either got empty response (-> socket closed) or and error
if not resp:
self.log.warning("Serial closed.")
self.machine.stop("Serial {} closed.".format(self.port))
if self.writer.is_closing():
self.log.error("Serial {} closed.".format(self.port))
self.machine.stop("Serial {} closed.".format(self.port))
return None

if self.debug:
Expand All @@ -142,7 +132,7 @@ def stop(self):
if self.read_task:
self.read_task.cancel()
self.read_task = None
if self.writer:
if self.writer and not self.writer.is_closing():
self.writer.close()
if hasattr(self.writer, "wait_closed"):
# Python 3.7+ only
Expand Down
21 changes: 11 additions & 10 deletions mpf/platforms/opp/opp.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,6 @@ async def _connect_to_hardware(self):
for port in self.config['ports']:
# overwrite serial if defined for port
overwrite_chain_serial = port_chain_serial_map.get(port, None)
if overwrite_chain_serial is None and len(self.config['ports']) == 1:
overwrite_chain_serial = port

comm = OPPSerialCommunicator(platform=self, port=port, baud=self.config['baud'],
overwrite_serial=overwrite_chain_serial)
Expand Down Expand Up @@ -955,20 +953,23 @@ async def _poll_sender(self, chain_serial):
# there is no point in polling without switches
return

poll_timeout = 1 / self.config['poll_hz'] * 25
pause_time = 1 / self.config['poll_hz']
while True:
# send poll
self.send_to_processor(chain_serial, self.read_input_msg[chain_serial])
await self.opp_connection[chain_serial].drain_writer()

# wait for previous poll response
timeout = 1 / self.config['poll_hz'] * 25
try:
await asyncio.wait_for(self._poll_response_received[chain_serial].wait(), timeout)
await asyncio.wait_for(self._poll_response_received[chain_serial].wait(), poll_timeout)
except asyncio.TimeoutError:
self.log.warning("Poll took more than %sms for %s", timeout * 1000, chain_serial)
self.log.warning("Poll took more than %sms for board #%s", poll_timeout * 1000, chain_serial)
else:
self._poll_response_received[chain_serial].clear()
# send poll
self.send_to_processor(chain_serial, self.read_input_msg[chain_serial])
await self.opp_connection[chain_serial].writer.drain()
# the line above saturates the link and seems to overwhelm the hardware. limit it to 100Hz
await asyncio.sleep(1 / self.config['poll_hz'])

# the line above saturates the link and seems to overwhelm the hardware.
await asyncio.sleep(pause_time)

def _verify_coil_and_switch_fit(self, switch, coil):
chain_serial, card, solenoid = coil.hw_driver.number.split('-')
Expand Down
21 changes: 16 additions & 5 deletions mpf/platforms/opp/opp_serial_communicator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""OPP serial communicator."""
import asyncio
from serial import SerialException

from mpf.platforms.opp.opp_rs232_intf import OppRs232Intf

Expand Down Expand Up @@ -74,8 +75,9 @@ async def _identify_connection(self):

self.log.debug("Got ID response: %s", "".join(HEX_FORMAT % b for b in resp))
if self.chain_serial is None:
# get ID from hardware if it is not overwritten
self.chain_serial = str(await self._read_id())
# get ID from hardware if it is not overwritten by config. If not available defaults to "0"
chain_serial = await self._read_id()
self.chain_serial = str(chain_serial) if chain_serial != 0xffffffff else "0"

if self.chain_serial in self.platform.opp_connection:
raise AssertionError("Duplicate chain serial {} on ports: {} and {}. Each OPP board has to have a "
Expand Down Expand Up @@ -118,7 +120,7 @@ async def _identify_connection(self):
self._create_vers_str(self.platform.min_version[self.chain_serial])))

# get initial value for inputs
self.log.debug("Getting initial inputs states for %s", self.chain_serial)
self.log.debug("Getting initial inputs states for #%s", self.chain_serial)
self.send(self.platform.read_input_msg[self.chain_serial])
cards = len([x for x in self.platform.opp_inputs if x.chain_serial == self.chain_serial])
while True:
Expand All @@ -127,8 +129,7 @@ async def _identify_connection(self):
if cards <= 0:
break
self.log.debug("Waiting for another %s cards", cards)

self.log.info("Init of OPP board %s done", self.chain_serial)
self.log.info("Init of OPP board #%s done on port %s", self.chain_serial, self.port)
self.platform.register_processor_connection(self.chain_serial, self)

def send_get_gen2_cfg_cmd(self):
Expand Down Expand Up @@ -231,3 +232,13 @@ def _parse_msg(self, msg):
self._lost_synch = True

return message_found

async def drain_writer(self):
"""Drain writer buffer."""
try:
await self.writer.drain()
except SerialException as e:
self.log.warning("Serial {} error: {}".format(self.port, e))
if self.writer.is_closing():
self.log.error("Serial {} closed.".format(self.port))
self.machine.stop("Serial {} closed.".format(self.port))
44 changes: 24 additions & 20 deletions mpf/tests/test_OPP.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ def setUp(self):
inputs2_message = b"\x21\x08\x00\x00\x00\x00"
inputs3a_message = b"\x23\x08\x00\x00\x00\x00"
inputs3b_message = b"\x23\x19\x00\x00\x00\x00\x00\x00\x00\x01"
getserial_message = b'\x20\x00\x00\x00\x00\x00'

self.serialMock.expected_commands = {
b'\xf0': b'\xf0\x20\x21\x22\x23', # boards 20 + 21 + 22 + 23 installed
Expand All @@ -372,6 +373,7 @@ def setUp(self):
}
self.serialMock.permanent_commands = {
b'\xff': b'\xff',
self._crc_message(getserial_message): self._crc_message(getserial_message),
self._crc_message(b'\x20\x08\x00\x00\x00\x00'): self._crc_message(inputs1_message),
self._crc_message(b'\x21\x08\x00\x00\x00\x00'): self._crc_message(inputs2_message),
self._crc_message(b'\x23\x08\x00\x00\x00\x00'): self._crc_message(inputs3a_message),
Expand All @@ -382,40 +384,40 @@ def setUp(self):
assert isinstance(self.machine.default_platform, OppHardwarePlatform)

self._wait_for_processing()
self.assertEqual(0x02000000, self.machine.default_platform.min_version["com1"])
self.assertEqual(0x02000000, self.machine.default_platform.min_version["0"])

self.assertFalse(self.serialMock.expected_commands)
self.maxDiff = 100000

# test hardware scan
info_str = """Connected CPUs:
- Port: com1 at 115200 baud. Chain Serial: com1
- Port: com1 at 115200 baud. Chain Serial: 0
-> Board: 0x20 Firmware: 0x2000000
-> Board: 0x21 Firmware: 0x2000000
-> Board: 0x22 Firmware: 0x2000000
-> Board: 0x23 Firmware: 0x2000000

Incand cards:
- Chain: com1 Board: 0x20 Card: 0 Numbers: [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
- Chain: com1 Board: 0x22 Card: 2 Numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,\
- Chain: 0 Board: 0x20 Card: 0 Numbers: [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
- Chain: 0 Board: 0x22 Card: 2 Numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,\
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]

Input cards:
- Chain: com1 Board: 0x20 Card: 0 Numbers: [0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15]
- Chain: com1 Board: 0x21 Card: 1 Numbers: [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\
- Chain: 0 Board: 0x20 Card: 0 Numbers: [0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15]
- Chain: 0 Board: 0x21 Card: 1 Numbers: [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\
22, 23, 24, 25, 26, 27]
- Chain: com1 Board: 0x23 Card: 3 Numbers: [0, 1, 2, 3, 8, 9, 10, 11]
- Chain: com1 Board: 0x23 Card: 3 Numbers: [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,\
- Chain: 0 Board: 0x23 Card: 3 Numbers: [0, 1, 2, 3, 8, 9, 10, 11]
- Chain: 0 Board: 0x23 Card: 3 Numbers: [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,\
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78,\
79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95]

Solenoid cards:
- Chain: com1 Board: 0x20 Card: 0 Numbers: [0, 1, 2, 3]
- Chain: com1 Board: 0x21 Card: 1 Numbers: [12, 13, 14, 15]
- Chain: com1 Board: 0x23 Card: 3 Numbers: [0, 1, 2, 3, 4, 5, 6, 7]
- Chain: 0 Board: 0x20 Card: 0 Numbers: [0, 1, 2, 3]
- Chain: 0 Board: 0x21 Card: 1 Numbers: [12, 13, 14, 15]
- Chain: 0 Board: 0x23 Card: 3 Numbers: [0, 1, 2, 3, 4, 5, 6, 7]

LEDs:
- Chain: com1 Board: 0x21 Card: 1
- Chain: 0 Board: 0x21 Card: 1
"""
self.assertEqual(info_str, self.machine.default_platform.get_info_string())

Expand Down Expand Up @@ -552,6 +554,7 @@ def setUp(self):
board2_version = b'\x21\x02\x00\x01\x01\x00' # 0.1.1.0
inputs1_message = b'\x20\x08\x00\x00\x00\x0c' # inputs 0+1 off, 2+3 on, 8 on
inputs2_message = b'\x21\x08\x00\x00\x00\x00'
getserial_message = b'\x20\x00\x00\x00\x00\x00'

self.serialMock.expected_commands = {
b'\xf0': b'\xf0\x20\x21', # boards 20 + 21 installed
Expand All @@ -569,6 +572,7 @@ def setUp(self):
}
self.serialMock.permanent_commands = {
b'\xff': b'\xff',
self._crc_message(getserial_message): self._crc_message(getserial_message),
self._crc_message(b'\x20\x08\x00\x00\x00\x00'): self._crc_message(inputs1_message),
self._crc_message(b'\x21\x08\x00\x00\x00\x00'): self._crc_message(inputs2_message), # read inputs
}
Expand All @@ -589,23 +593,23 @@ def test_opp(self):
# test hardware scan
self.maxDiff = 100000
info_str = """Connected CPUs:
- Port: com1 at 115200 baud. Chain Serial: com1
- Port: com1 at 115200 baud. Chain Serial: 0
-> Board: 0x20 Firmware: 0x10100
-> Board: 0x21 Firmware: 0x10100

Incand cards:
- Chain: com1 Board: 0x20 Card: 0 Numbers: [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
- Chain: 0 Board: 0x20 Card: 0 Numbers: [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]

Input cards:
- Chain: com1 Board: 0x20 Card: 0 Numbers: [0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15]
- Chain: com1 Board: 0x21 Card: 1 Numbers: [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]
- Chain: 0 Board: 0x20 Card: 0 Numbers: [0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15]
- Chain: 0 Board: 0x21 Card: 1 Numbers: [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]

Solenoid cards:
- Chain: com1 Board: 0x20 Card: 0 Numbers: [0, 1, 2, 3]
- Chain: com1 Board: 0x21 Card: 1 Numbers: [12, 13, 14, 15]
- Chain: 0 Board: 0x20 Card: 0 Numbers: [0, 1, 2, 3]
- Chain: 0 Board: 0x21 Card: 1 Numbers: [12, 13, 14, 15]

LEDs:
- Chain: com1 Board: 0x21 Card: 1
- Chain: 0 Board: 0x21 Card: 1
"""
self.assertEqual(info_str, self.machine.default_platform.get_info_string())

Expand Down Expand Up @@ -641,7 +645,7 @@ def _test_switches(self):
self.serialMock.permanent_commands = permanent_commands

def _test_coils(self):
self.assertEqual("OPP com1 Board 0x20", self.machine.coils["c_test"].hw_driver.get_board_name())
self.assertEqual("OPP 0 Board 0x20", self.machine.coils["c_test"].hw_driver.get_board_name())
# pulse coil
self.serialMock.expected_commands[self._crc_message(b'\x20\x14\x00\x02\x17\x00')] = False # configure coil 0
self.serialMock.expected_commands[self._crc_message(b'\x20\x07\x00\x01\x00\x01')] = False
Expand Down
Loading