Browse Source

Merge branch 'dev'

pyserial_fix
Scott Petersen 9 years ago
parent
commit
4aced9bdb3
6 changed files with 179 additions and 63 deletions
  1. +6
    -6
      alarmdecoder/decoder.py
  2. +38
    -2
      alarmdecoder/devices.py
  3. +68
    -21
      alarmdecoder/util.py
  4. +28
    -11
      bin/ad2-firmwareupload
  5. +33
    -22
      test/test_devices.py
  6. +6
    -1
      test/test_zonetracking.py

+ 6
- 6
alarmdecoder/decoder.py View File

@@ -104,13 +104,13 @@ class AlarmDecoder(object):
self._armed_status = None self._armed_status = None
self._fire_status = (False, 0) self._fire_status = (False, 0)
self._battery_status = (False, 0) self._battery_status = (False, 0)
self._panic_status = None
self._panic_status = False
self._relay_status = {} self._relay_status = {}
self._internal_address_mask = 0xFFFFFFFF self._internal_address_mask = 0xFFFFFFFF


self.address = 18 self.address = 18
self.configbits = 0xFF00 self.configbits = 0xFF00
self.address_mask = 0x00000000
self.address_mask = 0xFFFFFFF
self.emulate_zone = [False for x in range(5)] self.emulate_zone = [False for x in range(5)]
self.emulate_relay = [False for x in range(4)] self.emulate_relay = [False for x in range(4)]
self.emulate_lrr = False self.emulate_lrr = False
@@ -243,7 +243,9 @@ class AlarmDecoder(object):
""" """
Sets configuration entries on the device. Sets configuration entries on the device.
""" """
config_string = ''
self.send("C{0}\r".format(self.get_config_string()))

def get_config_string(self):
config_entries = [] config_entries = []


# HACK: This is ugly.. but I can't think of an elegant way of doing it. # HACK: This is ugly.. but I can't think of an elegant way of doing it.
@@ -258,9 +260,7 @@ class AlarmDecoder(object):
config_entries.append(('DEDUPLICATE', 'Y' if self.deduplicate else 'N')) config_entries.append(('DEDUPLICATE', 'Y' if self.deduplicate else 'N'))
config_entries.append(('MODE', PANEL_TYPES.keys()[PANEL_TYPES.values().index(self.mode)])) config_entries.append(('MODE', PANEL_TYPES.keys()[PANEL_TYPES.values().index(self.mode)]))


config_string = '&'.join(['='.join(t) for t in config_entries])

self.send("C{0}\r".format(config_string))
return '&'.join(['='.join(t) for t in config_entries])


def reboot(self): def reboot(self):
""" """


+ 38
- 2
alarmdecoder/devices.py View File

@@ -20,6 +20,7 @@ import threading
import serial import serial
import serial.tools.list_ports import serial.tools.list_ports
import socket import socket
import select


from .util import CommError, TimeoutError, NoDeviceError, InvalidMessageError from .util import CommError, TimeoutError, NoDeviceError, InvalidMessageError
from .event import event from .event import event
@@ -559,6 +560,12 @@ class USBDevice(Device):


return ret return ret


def purge(self):
"""
Purges read/write buffers.
"""
self._device.purge_buffers()

def _get_serial_number(self): def _get_serial_number(self):
""" """
Retrieves the FTDI device serial number. Retrieves the FTDI device serial number.
@@ -849,6 +856,13 @@ class SerialDevice(Device):


return ret return ret


def purge(self):
"""
Purges read/write buffers.
"""
self._device.flushInput()
self._device.flushOutput()



class SocketDevice(Device): class SocketDevice(Device):
""" """
@@ -989,7 +1003,6 @@ class SocketDevice(Device):
self._init_ssl() self._init_ssl()


self._device.connect((self._host, self._port)) self._device.connect((self._host, self._port))
#self._device.setblocking(1)


if self._use_ssl: if self._use_ssl:
while True: while True:
@@ -1069,7 +1082,10 @@ class SocketDevice(Device):
data = None data = None


try: try:
data = self._device.recv(1)
read_ready, _, _ = select.select([self._device], [], [], 0)

if (len(read_ready) != 0):
data = self._device.recv(1)


except socket.error, err: except socket.error, err:
raise CommError('Error while reading from device: {0}'.format(str(err)), err) raise CommError('Error while reading from device: {0}'.format(str(err)), err)
@@ -1106,6 +1122,12 @@ class SocketDevice(Device):


try: try:
while timeout_event.reading: while timeout_event.reading:
read_ready, _, _ = select.select([self._device], [], [], 0)

if (len(read_ready) == 0):
time.sleep(0.01)
continue

buf = self._device.recv(1) buf = self._device.recv(1)


if buf != '': if buf != '':
@@ -1117,6 +1139,7 @@ class SocketDevice(Device):
if len(self._buffer) > 0: if len(self._buffer) > 0:
got_line = True got_line = True
break break

else: else:
time.sleep(0.01) time.sleep(0.01)


@@ -1144,6 +1167,19 @@ class SocketDevice(Device):


return ret return ret


def purge(self):
"""
Purges read/write buffers.
"""
try:
self._device.setblocking(0)
while(self._device.recv(1)):
pass
except socket.error, err:
pass
finally:
self._device.setblocking(1)

def _init_ssl(self): def _init_ssl(self):
""" """
Initializes our device as an SSL connection. Initializes our device as an SSL connection.


+ 68
- 21
alarmdecoder/util.py View File

@@ -38,6 +38,20 @@ class InvalidMessageError(Exception):
pass pass




class UploadError(Exception):
"""
Generic firmware upload error.
"""
pass


class UploadChecksumError(UploadError):
"""
The firmware upload failed due to a checksum error.
"""
pass


class Firmware(object): class Firmware(object):
""" """
Represents firmware for the `AlarmDecoder`_ devices. Represents firmware for the `AlarmDecoder`_ devices.
@@ -50,10 +64,12 @@ class Firmware(object):
STAGE_LOAD = 3 STAGE_LOAD = 3
STAGE_UPLOADING = 4 STAGE_UPLOADING = 4
STAGE_DONE = 5 STAGE_DONE = 5
STAGE_ERROR = 98
STAGE_DEBUG = 99


# FIXME: Rewrite this monstrosity. # FIXME: Rewrite this monstrosity.
@staticmethod @staticmethod
def upload(dev, filename, progress_callback=None):
def upload(dev, filename, progress_callback=None, debug=False):
""" """
Uploads firmware to an `AlarmDecoder`_ device. Uploads firmware to an `AlarmDecoder`_ device.


@@ -70,15 +86,29 @@ class Firmware(object):
Perform the actual firmware upload to the device. Perform the actual firmware upload to the device.
""" """
with open(filename) as upload_file: with open(filename) as upload_file:
line_cnt = 0
for line in upload_file: for line in upload_file:
line_cnt += 1
line = line.rstrip() line = line.rstrip()


if line[0] == ':': if line[0] == ':':
dev.write(line + "\r") dev.write(line + "\r")
dev.read_line(timeout=10.0)
response = dev.read_line(timeout=5.0, purge_buffer=True)
if debug:
stage_callback(Firmware.STAGE_DEBUG, data="line={0} - line={1} response={2}".format(line_cnt, line, response));

if '!ce' in response:
raise UploadChecksumError("Checksum error on line " + str(line_cnt) + " of " + filename);

elif '!no' in response:
raise UploadError("Incorrect data sent to bootloader.")

elif '!ok' in response:
break


if progress_callback is not None:
progress_callback(Firmware.STAGE_UPLOADING)
else:
if progress_callback is not None:
progress_callback(Firmware.STAGE_UPLOADING)


time.sleep(0.0) time.sleep(0.0)


@@ -100,6 +130,8 @@ class Firmware(object):


position = 0 position = 0


dev.purge()

while timeout_event.reading: while timeout_event.reading:
try: try:
char = dev.read() char = dev.read()
@@ -112,7 +144,7 @@ class Firmware(object):
else: else:
position = 0 position = 0


except Exception:
except Exception, err:
pass pass


if timer: if timer:
@@ -121,10 +153,10 @@ class Firmware(object):
else: else:
raise TimeoutError('Timeout while waiting for line terminator.') raise TimeoutError('Timeout while waiting for line terminator.')


def stage_callback(stage):
def stage_callback(stage, **kwargs):
"""Callback to update progress for the specified stage.""" """Callback to update progress for the specified stage."""
if progress_callback is not None: if progress_callback is not None:
progress_callback(stage)
progress_callback(stage, **kwargs)


if dev is None: if dev is None:
raise NoDeviceError('No device specified for firmware upload.') raise NoDeviceError('No device specified for firmware upload.')
@@ -137,21 +169,36 @@ class Firmware(object):
dev.stop_reader() dev.stop_reader()
while dev._read_thread.is_alive(): while dev._read_thread.is_alive():
stage_callback(Firmware.STAGE_WAITING) stage_callback(Firmware.STAGE_WAITING)
time.sleep(1)

time.sleep(2)
time.sleep(0.5)


# Reboot the device and wait for the boot loader. # Reboot the device and wait for the boot loader.
stage_callback(Firmware.STAGE_BOOT)
dev.write("=")
read_until('......', timeout=15.0)

# Get ourselves into the boot loader and wait for indication
# that it's ready for the firmware upload.
stage_callback(Firmware.STAGE_LOAD)
dev.write("=")
read_until('!load', timeout=15.0)
retry = 3
found_loader = False
while retry > 0:
try:
stage_callback(Firmware.STAGE_BOOT)
dev.write("=")
read_until('!boot', timeout=15.0)

# Get ourselves into the boot loader and wait for indication
# that it's ready for the firmware upload.
stage_callback(Firmware.STAGE_LOAD)
dev.write("=")
read_until('!load', timeout=15.0)

except TimeoutError, err:
retry -= 1
else:
retry = 0
found_loader = True


# And finally do the upload. # And finally do the upload.
do_upload()
stage_callback(Firmware.STAGE_DONE)
if found_loader:
try:
do_upload()
except UploadError, err:
stage_callback(Firmware.STAGE_ERROR, error=str(err))
else:
stage_callback(Firmware.STAGE_DONE)
else:
stage_callback(Firmware.STAGE_ERROR, error="Error entering bootloader.")

+ 28
- 11
bin/ad2-firmwareupload View File

@@ -1,9 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python


import os
import sys, time import sys, time
import alarmdecoder import alarmdecoder


def handle_firmware(stage):
def handle_firmware(stage, **kwargs):
if stage == alarmdecoder.util.Firmware.STAGE_START: if stage == alarmdecoder.util.Firmware.STAGE_START:
handle_firmware.wait_tick = 0 handle_firmware.wait_tick = 0
handle_firmware.upload_tick = 0 handle_firmware.upload_tick = 0
@@ -30,6 +31,10 @@ def handle_firmware(stage):
sys.stdout.flush() sys.stdout.flush()
elif stage == alarmdecoder.util.Firmware.STAGE_DONE: elif stage == alarmdecoder.util.Firmware.STAGE_DONE:
print "\r\nDone!" print "\r\nDone!"
elif stage == alarmdecoder.util.Firmware.STAGE_ERROR:
print "\r\nError: {0}".format(kwargs.get("error", ""))
elif stage == alarmdecoder.util.Firmware.STAGE_DEBUG:
print "\r\nDBG: {0}".format(kwargs.get("data", ""))


def main(): def main():
device = '/dev/ttyUSB0' device = '/dev/ttyUSB0'
@@ -47,20 +52,32 @@ def main():
if len(sys.argv) > 3: if len(sys.argv) > 3:
baudrate = sys.argv[3] baudrate = sys.argv[3]


debug = os.environ.get("ALARMDECODER_DEBUG") is not None

print "Flashing device: {0} - {2} baud\r\nFirmware: {1}".format(device, firmware, baudrate) print "Flashing device: {0} - {2} baud\r\nFirmware: {1}".format(device, firmware, baudrate)


if ':' in device:
hostname, port = device.split(':')
dev = alarmdecoder.devices.SocketDevice(interface=(hostname, int(port)))
dev.open()
else:
dev = alarmdecoder.devices.SerialDevice(interface=device)
dev.open(baudrate=baudrate, no_reader_thread=True)
dev = None
try:
if ':' in device:
hostname, port = device.split(':')
dev = alarmdecoder.devices.SocketDevice(interface=(hostname, int(port)))
dev.open(no_reader_thread=True)
else:
dev = alarmdecoder.devices.SerialDevice(interface=device)
dev.open(baudrate=baudrate, no_reader_thread=True)


time.sleep(3)
alarmdecoder.util.Firmware.upload(dev, firmware, handle_firmware)
time.sleep(3)
alarmdecoder.util.Firmware.upload(dev, firmware, handle_firmware, debug=debug)


dev.close()
except alarmdecoder.util.NoDeviceError, ex:
print "Error: Could not find device: {0}".format(ex)
except alarmdecoder.util.UploadError, ex:
print "Error: Error uploading firmware: {0}".format(ex)
except Exception, ex:
print "Error: {0}".format(ex)
finally:
if dev is not None:
dev.close()


if __name__ == "__main__": if __name__ == "__main__":
main() main()

+ 33
- 22
test/test_devices.py View File

@@ -7,6 +7,7 @@ import socket
import time import time
import tempfile import tempfile
import os import os
import select
from OpenSSL import SSL, crypto from OpenSSL import SSL, crypto
from alarmdecoder.devices import USBDevice, SerialDevice, SocketDevice from alarmdecoder.devices import USBDevice, SerialDevice, SocketDevice
from alarmdecoder.util import NoDeviceError, CommError, TimeoutError from alarmdecoder.util import NoDeviceError, CommError, TimeoutError
@@ -292,40 +293,50 @@ class TestSocketDevice(TestCase):
with patch.object(socket.socket, 'connect', return_value=None): with patch.object(socket.socket, 'connect', return_value=None):
self._device.open(no_reader_thread=True) self._device.open(no_reader_thread=True)


with patch.object(socket.socket, 'recv') as mock:
self._device.read()
with patch('socket.socket.fileno', return_value=1):
with patch.object(select, 'select', return_value=[[1], [], []]):
with patch.object(socket.socket, 'recv') as mock:
self._device.read()


mock.assert_called_with(1) mock.assert_called_with(1)


def test_read_exception(self): def test_read_exception(self):
with patch.object(self._device._device, 'recv', side_effect=socket.error):
with self.assertRaises(CommError):
self._device.read()
with patch('socket.socket.fileno', return_value=1):
with patch.object(select, 'select', return_value=[[1], [], []]):
with patch.object(self._device._device, 'recv', side_effect=socket.error):
with self.assertRaises(CommError):
self._device.read()


def test_read_line(self): def test_read_line(self):
with patch.object(self._device._device, 'recv', side_effect=list("testing\r\n")):
ret = None
try:
ret = self._device.read_line()
except StopIteration:
pass
with patch('socket.socket.fileno', return_value=1):
with patch.object(select, 'select', return_value=[[1], [], []]):
with patch.object(self._device._device, 'recv', side_effect=list("testing\r\n")):
ret = None
try:
ret = self._device.read_line()
except StopIteration:
pass


self.assertEquals(ret, "testing")
self.assertEquals(ret, "testing")


def test_read_line_timeout(self): def test_read_line_timeout(self):
with patch.object(self._device._device, 'recv', return_value='a') as mock:
with self.assertRaises(TimeoutError):
self._device.read_line(timeout=0.1)
with patch('socket.socket.fileno', return_value=1):
with patch.object(select, 'select', return_value=[[1], [], []]):
with patch.object(self._device._device, 'recv', return_value='a') as mock:
with self.assertRaises(TimeoutError):
self._device.read_line(timeout=0.1)


self.assertIn('a', self._device._buffer)
self.assertIn('a', self._device._buffer)


def test_read_line_exception(self): def test_read_line_exception(self):
with patch.object(self._device._device, 'recv', side_effect=socket.error):
with self.assertRaises(CommError):
self._device.read_line()

with self.assertRaises(CommError):
self._device.read_line()
with patch('socket.socket.fileno', return_value=1):
with patch.object(select, 'select', return_value=[[1], [], []]):
with patch.object(self._device._device, 'recv', side_effect=socket.error):
with self.assertRaises(CommError):
self._device.read_line()

with self.assertRaises(CommError):
self._device.read_line()


def test_ssl(self): def test_ssl(self):
ssl_key = crypto.PKey() ssl_key = crypto.PKey()


+ 6
- 1
test/test_zonetracking.py View File

@@ -1,13 +1,18 @@
from unittest import TestCase from unittest import TestCase
from mock import Mock, MagicMock from mock import Mock, MagicMock


from alarmdecoder import AlarmDecoder
from alarmdecoder.panels import ADEMCO
from alarmdecoder.messages import Message, ExpanderMessage from alarmdecoder.messages import Message, ExpanderMessage
from alarmdecoder.zonetracking import Zonetracker, Zone from alarmdecoder.zonetracking import Zonetracker, Zone




class TestZonetracking(TestCase): class TestZonetracking(TestCase):
def setUp(self): def setUp(self):
self._zonetracker = Zonetracker()
self._alarmdecoder = Mock(spec=AlarmDecoder)

self._alarmdecoder.mode = ADEMCO
self._zonetracker = Zonetracker(self._alarmdecoder)


self._zonetracker.on_fault += self.fault_event self._zonetracker.on_fault += self.fault_event
self._zonetracker.on_restore += self.restore_event self._zonetracker.on_restore += self.restore_event


Loading…
Cancel
Save