Browse Source

Cleanup, comments.

pyserial_fix
Scott Petersen 7 years ago
parent
commit
08519474e2
8 changed files with 240 additions and 72 deletions
  1. +32
    -5
      alarmdecoder/decoder.py
  2. +15
    -0
      alarmdecoder/devices.py
  3. +2
    -2
      alarmdecoder/messages/lrr/__init__.py
  4. +80
    -13
      alarmdecoder/messages/lrr/events.py
  5. +10
    -21
      alarmdecoder/messages/lrr/message.py
  6. +73
    -31
      alarmdecoder/messages/lrr/system.py
  7. +3
    -0
      alarmdecoder/states.py
  8. +25
    -0
      alarmdecoder/util.py

+ 32
- 5
alarmdecoder/decoder.py View File

@@ -92,6 +92,8 @@ class AlarmDecoder(object):
"""The status of message deduplication as configured on the device."""
mode = ADEMCO
"""The panel mode that the AlarmDecoder is in. Currently supports ADEMCO and DSC."""
emulate_com = False
"""The status of the devices COM emulation."""

#Version Information
serial_number = 0xFFFFFFFF
@@ -101,10 +103,6 @@ class AlarmDecoder(object):
version_flags = ""
"""Device flags enabled"""

FIRE_STATE_NONE = 0
FIRE_STATE_FIRE = 1
FIRE_STATE_ACKNOWLEDGED = 2

def __init__(self, device, ignore_message_states=False):
"""
Constructor
@@ -112,6 +110,8 @@ class AlarmDecoder(object):
:param device: The low-level device used for this `AlarmDecoder`_
interface.
:type device: Device
:param ignore_message_states: Ignore regular panel messages when updating internal states
:type ignore_message_states: bool
"""
self._device = device
self._zonetracker = Zonetracker(self)
@@ -142,6 +142,7 @@ class AlarmDecoder(object):
self.emulate_lrr = False
self.deduplicate = False
self.mode = ADEMCO
self.emulate_com = False

self.serial_number = 0xFFFFFFFF
self.version_number = 'Unknown'
@@ -284,6 +285,12 @@ class AlarmDecoder(object):
self.send("C{0}\r".format(self.get_config_string()))

def get_config_string(self):
"""
Build a configuration string that's compatible with the AlarmDecoder configuration
command from the current values in the object.

:returns: string
"""
config_entries = []

# HACK: This is ugly.. but I can't think of an elegant way of doing it.
@@ -297,6 +304,7 @@ class AlarmDecoder(object):
config_entries.append(('LRR', 'Y' if self.emulate_lrr else 'N'))
config_entries.append(('DEDUPLICATE', 'Y' if self.deduplicate else 'N'))
config_entries.append(('MODE', list(PANEL_TYPES)[list(PANEL_TYPES.values()).index(self.mode)]))
config_entries.append(('COM', 'Y' if self.emulate_com else 'N'))

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

@@ -515,6 +523,8 @@ class AlarmDecoder(object):
self.deduplicate = (val == 'Y')
elif key == 'MODE':
self.mode = PANEL_TYPES[val]
elif key == 'COM':
self.emulate_com = (val == 'Y')

self.on_config_received()

@@ -560,6 +570,8 @@ class AlarmDecoder(object):

:param message: message to use to update
:type message: :py:class:`~alarmdecoder.messages.Message`
:param status: power status, overrides message bits.
:type status: bool

:returns: bool indicating the new status
"""
@@ -584,6 +596,10 @@ class AlarmDecoder(object):

:param message: message to use to update
:type message: :py:class:`~alarmdecoder.messages.Message`
:param status: alarm status, overrides message bits.
:type status: bool
:param user: user associated with alarm event
:type user: string

:returns: bool indicating the new status
"""
@@ -611,6 +627,10 @@ class AlarmDecoder(object):

:param message: message to use to update
:type message: :py:class:`~alarmdecoder.messages.Message`
:param status: bypass status, overrides message bits.
:type status: bool
:param zone: zone associated with bypass event
:type zone: int

:returns: bool indicating the new status
"""
@@ -640,6 +660,10 @@ class AlarmDecoder(object):

:param message: message to use to update
:type message: :py:class:`~alarmdecoder.messages.Message`
:param status: armed status, overrides message bits
:type status: bool
:param status_stay: armed stay status, overrides message bits
:type status_stay: bool

:returns: bool indicating the new status
"""
@@ -670,6 +694,8 @@ class AlarmDecoder(object):

:param message: message to use to update
:type message: :py:class:`~alarmdecoder.messages.Message`
:param status: battery status, overrides message bits
:type status: bool

:returns: boolean indicating the new status
"""
@@ -696,6 +722,8 @@ class AlarmDecoder(object):

:param message: message to use to update
:type message: :py:class:`~alarmdecoder.messages.Message`
:param status: fire status, overrides message bits
:type status: bool

:returns: boolean indicating the new status
"""
@@ -807,7 +835,6 @@ class AlarmDecoder(object):
Internal handler for opening the device.
"""
self.get_config()

self.get_version()

self.on_open()


+ 15
- 0
alarmdecoder/devices.py View File

@@ -495,6 +495,11 @@ class USBDevice(Device):
pass

def fileno(self):
"""
File number not supported for USB devices.
:raises: NotImplementedError
"""
raise NotImplementedError('USB devices do not support fileno()')

def write(self, data):
@@ -788,6 +793,11 @@ class SerialDevice(Device):
pass

def fileno(self):
"""
Returns the file number associated with the device
:returns: int
"""
return self._device.fileno()

def write(self, data):
@@ -1103,6 +1113,11 @@ class SocketDevice(Device):
Device.close(self)

def fileno(self):
"""
Returns the file number associated with the device
:returns: int
"""
return self._device.fileno()

def write(self, data):


+ 2
- 2
alarmdecoder/messages/lrr/__init__.py View File

@@ -1,9 +1,9 @@
from .message import LRRMessage
from .system import LRRSystem
from .events import get_event_description, LRR_EVENT_TYPE, LRR_CID_EVENT, LRR_DSC_EVENT, LRR_ADEMCO_EVENT, \
from .events import get_event_description, get_event_source, LRR_EVENT_TYPE, LRR_EVENT_STATUS, LRR_CID_EVENT, LRR_DSC_EVENT, LRR_ADEMCO_EVENT, \
LRR_ALARMDECODER_EVENT, LRR_UNKNOWN_EVENT, LRR_CID_MAP, LRR_DSC_MAP, LRR_ADEMCO_MAP, \
LRR_ALARMDECODER_MAP, LRR_UNKNOWN_MAP

__all__ = ['get_event_description', 'LRRMessage', 'LRR_EVENT_TYPE', 'LRR_CID_EVENT', 'LRR_DSC_EVENT',
__all__ = ['get_event_description', 'get_event_source', 'LRRMessage', 'LRR_EVENT_TYPE', 'LRR_EVENT_STATUS', 'LRR_CID_EVENT', 'LRR_DSC_EVENT',
'LRR_ADEMCO_EVENT', 'LRR_ALARMDECODER_EVENT', 'LRR_UNKNOWN_EVENT', 'LRR_CID_MAP',
'LRR_DSC_MAP', 'LRR_ADEMCO_MAP', 'LRR_ALARMDECODER_MAP', 'LRR_UNKNOWN_MAP']

+ 80
- 13
alarmdecoder/messages/lrr/events.py View File

@@ -4,35 +4,76 @@ Constants and utility functions used for LRR event handling.
.. moduleauthor:: Scott Petersen <scott@nutech.com>
"""

def get_event_description(event_type, value):
description = 'Unknown'
lookup_map = None
def get_event_description(event_type, event_code):
"""
Retrieves the human-readable description of an LRR event.

:param event_type: Base LRR event type. Use LRR_EVENT_TYPE.*
:type event_type: int
:param event_code: LRR event code
:type event_code: int

if event_type in LRR_TYPE_MAP.keys():
lookup_map = LRR_TYPE_MAP[event_type]
:returns: string
"""
description = 'Unknown'
lookup_map = LRR_TYPE_MAP.get(event_type, None)

if value in lookup_map.keys():
description = lookup_map[value]
if lookup_map is not None:
description = lookup_map.get(event_code, description)

return description

def get_event_source(prefix):
"""
Retrieves the LRR_EVENT_TYPE corresponding to the prefix provided.abs

:param prefix: Prefix to convert to event type
:type prefix: string

:returns: int
"""
source = LRR_EVENT_TYPE.UNKNOWN

if prefix == 'CID':
source = LRR_EVENT_TYPE.CID
elif prefix == 'DSC':
source = LRR_EVENT_TYPE.DSC
elif prefix == 'AD2':
source = LRR_EVENT_TYPE.ALARMDECODER
elif prefix == 'ADEMCO':
source = LRR_EVENT_TYPE.ADEMCO

return source


class LRR_EVENT_TYPE:
"""
Base LRR event types
"""
CID = 1
DSC = 2
ADEMCO = 3
ALARMDECODER = 4
UNKNOWN = 5


class LRR_EVENT_STATUS:
"""
LRR event status codes
"""
TRIGGER = 1
RESTORE = 3


class LRR_CID_EVENT:
"""
ContactID event codes
"""
MEDICAL = 0x100
MEDICAL_PENDANT = 0x101
MEDICAL_FAIL_TO_REPORT = 0x102
# 103-108: ?
TAMPER_ZONE = 0x109 # Where did we find this?
TAMPER_ZONE = 0x109 # NOTE: Where did we find this?
FIRE = 0x110
FIRE_SMOKE = 0x111
FIRE_COMBUSTION = 0x112
@@ -231,7 +272,8 @@ class LRR_CID_EVENT:
STATUS_PANIC_ALARM_RESET = 0x465
ACCESS_SERVICE_ONOFF_PREMISES = 0x466
# 467-469: ?
OPENCLOSE_PARTIAL_CLOSING = 0x470 # HACK: This is from DSC, and is named far too closely to 0
OPENCLOSE_PARTIAL_CLOSING = 0x470 # HACK: This is from our DSC firmware implementation,
# and is named far too closely to 0x480.
# 471-479: ?
OPENCLOSE_PARTIAL_CLOSE = 0x480
# 481-500: ?
@@ -343,6 +385,9 @@ class LRR_CID_EVENT:


class LRR_DSC_EVENT:
"""
DSC event codes
"""
ZONE_EXPANDER_SUPERVISORY_ALARM = 0x04c
ZONE_EXPANDER_SUPERVISORY_RESTORE = 0x04d
AUX_INPUT_ALARM = 0x051
@@ -354,18 +399,28 @@ class LRR_DSC_EVENT:


class LRR_ADEMCO_EVENT:
"""
ADEMCO event codes
"""
pass


class LRR_ALARMDECODER_EVENT:
"""
AlarmDecoder event codes
"""
CUSTOM_PROG_MSG = 0x0
CUSTOM_PROG_KEY = 0x1


class LRR_UNKNOWN_EVENT:
"""
Unknown event codes. Realistically there shouldn't ever be anything here.
"""
pass


# Map of ContactID event codes to human-readable text.
LRR_CID_MAP = {
LRR_CID_EVENT.MEDICAL: 'Medical Emergency: Non-specific',
LRR_CID_EVENT.MEDICAL_PENDANT: 'Emergency Assistance Request',
@@ -640,6 +695,7 @@ LRR_CID_MAP = {
LRR_CID_EVENT.OTHER_NO_READ_LOG: 'Other: No Read Log',
}

# Map of DSC event codes to human-readable text.
LRR_DSC_MAP = {
LRR_DSC_EVENT.ZONE_EXPANDER_SUPERVISORY_ALARM: 'Zone Expander Supervisory Alarm',
LRR_DSC_EVENT.ZONE_EXPANDER_SUPERVISORY_RESTORE: 'Zone Expander Supervisory Restore',
@@ -651,6 +707,7 @@ LRR_DSC_MAP = {
LRR_DSC_EVENT.REPORT_DSC_USER_LOG_EVENT: 'Report DSC User Log Event',
}

# Map of ADEMCO event codes to human-readable text.
LRR_ADEMCO_MAP = {

}
@@ -660,10 +717,12 @@ LRR_ALARMDECODER_MAP = {
LRR_ALARMDECODER_EVENT.CUSTOM_PROG_KEY: 'Custom Programming Key'
}

# Map of UNKNOWN event codes to human-readable text.
LRR_UNKNOWN_MAP = {

}

# Map of event type codes to text maps.
LRR_TYPE_MAP = {
LRR_EVENT_TYPE.CID: LRR_CID_MAP,
LRR_EVENT_TYPE.DSC: LRR_DSC_MAP,
@@ -672,6 +731,7 @@ LRR_TYPE_MAP = {
LRR_EVENT_TYPE.UNKNOWN: LRR_UNKNOWN_MAP,
}

# LRR events that should be considered Fire events.
LRR_FIRE_EVENTS = [
LRR_CID_EVENT.FIRE,
LRR_CID_EVENT.FIRE_SMOKE,
@@ -681,9 +741,10 @@ LRR_FIRE_EVENTS = [
LRR_CID_EVENT.FIRE_PULL_STATION,
LRR_CID_EVENT.FIRE_DUCT,
LRR_CID_EVENT.FIRE_FLAME,
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER # HACK: Don't really like having this here
]

# LRR events that should be considered Alarm events.
LRR_ALARM_EVENTS = [
LRR_CID_EVENT.BURGLARY,
LRR_CID_EVENT.BURGLARY_PERIMETER,
@@ -704,23 +765,27 @@ LRR_ALARM_EVENTS = [
LRR_CID_EVENT.ALARM_LOW_TEMP,
LRR_CID_EVENT.ALARM_LOSS_OF_AIR_FLOW,
LRR_CID_EVENT.ALARM_CARBON_MONOXIDE,
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER # HACK: Don't really like having this here
]

# LRR events that should be considered Power events.
LRR_POWER_EVENTS = [
LRR_CID_EVENT.TROUBLE_AC_LOSS
]

# LRR events that should be considered Bypass events.
LRR_BYPASS_EVENTS = [
LRR_CID_EVENT.BYPASS_ZONE,
LRR_CID_EVENT.BYPASS_24HOUR_ZONE,
LRR_CID_EVENT.BYPASS_BURGLARY
]

# LRR events that should be considered Battery events.
LRR_BATTERY_EVENTS = [
LRR_CID_EVENT.TROUBLE_LOW_BATTERY
]

# LRR events that should be considered Panic events.
LRR_PANIC_EVENTS = [
LRR_CID_EVENT.MEDICAL,
LRR_CID_EVENT.MEDICAL_PENDANT,
@@ -731,9 +796,10 @@ LRR_PANIC_EVENTS = [
LRR_CID_EVENT.PANIC_AUDIBLE,
LRR_CID_EVENT.PANIC_DURESS_ACCESS_GRANTED,
LRR_CID_EVENT.PANIC_DURESS_EGRESS_GRANTED,
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER # Canceled panic
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER # HACK: Don't really like having this here
]

# LRR events that should be considered Arm events.
LRR_ARM_EVENTS = [
LRR_CID_EVENT.OPENCLOSE,
LRR_CID_EVENT.OPENCLOSE_BY_USER,
@@ -742,10 +808,11 @@ LRR_ARM_EVENTS = [
LRR_CID_EVENT.OPENCLOSE_REMOTE_ARMDISARM,
LRR_CID_EVENT.OPENCLOSE_QUICK_ARM,
LRR_CID_EVENT.OPENCLOSE_KEYSWITCH,
LRR_CID_EVENT.OPENCLOSE_ARMED_STAY,
LRR_CID_EVENT.OPENCLOSE_ARMED_STAY, # HACK: Not sure if I like having these in here.
LRR_CID_EVENT.OPENCLOSE_KEYSWITCH_ARMED_STAY
]

# LRR events that should be considered Arm Stay events.
LRR_STAY_EVENTS = [
LRR_CID_EVENT.OPENCLOSE_ARMED_STAY,
LRR_CID_EVENT.OPENCLOSE_KEYSWITCH_ARMED_STAY


+ 10
- 21
alarmdecoder/messages/lrr/message.py View File

@@ -12,7 +12,7 @@ devices.
from .. import BaseMessage
from ...util import InvalidMessageError

from .events import LRR_EVENT_TYPE, get_event_description
from .events import LRR_EVENT_TYPE, get_event_description, get_event_source


class LRRMessage(BaseMessage):
@@ -70,18 +70,22 @@ class LRRMessage(BaseMessage):

_, values = data.split(':')
values = values.split(',')

# Handle older-format events
if len(values) <= 3:
self.event_data, self.partition, self.event_type = values
self.version = 1

# Newer-format events
else:
self.event_data, self.partition, self.event_type, self.report_code = values
self.version = 2

event_type_data = self.event_type.split('_')
self.event_prefix = event_type_data[0]
self.event_source = _get_event_source(self.event_prefix)
self.event_status = int(event_type_data[1][0])
self.event_code = int(event_type_data[1][1:], 16)
self.event_prefix = event_type_data[0] # Ex: CID
self.event_source = get_event_source(self.event_prefix) # Ex: LRR_EVENT_TYPE.CID
self.event_status = int(event_type_data[1][0]) # Ex: 1 or 3
self.event_code = int(event_type_data[1][1:], 16) # Ex: 0x100 = Medical

# replace last 2 digits of event_code with report_code, if applicable.
if not self.skip_report_override and self.report_code not in ['00', 'ff']:
@@ -94,7 +98,7 @@ class LRRMessage(BaseMessage):

def dict(self, **kwargs):
"""
Dictionary representation.
Dictionary representation
"""
return dict(
time = self.timestamp,
@@ -109,18 +113,3 @@ class LRRMessage(BaseMessage):
event_description = self.event_description,
**kwargs
)


def _get_event_source(prefix):
source = LRR_EVENT_TYPE.UNKNOWN

if prefix == 'CID':
source = LRR_EVENT_TYPE.CID
elif prefix == 'DSC':
source = LRR_EVENT_TYPE.DSC
elif prefix == 'AD2':
source = LRR_EVENT_TYPE.ALARMDECODER
elif prefix == 'ADEMCO':
source = LRR_EVENT_TYPE.ADEMCO

return source

+ 73
- 31
alarmdecoder/messages/lrr/system.py View File

@@ -1,3 +1,8 @@
"""
Primary system for handling LRR events.

.. moduleauthor:: Scott Petersen <scott@nutech.com>
"""

from .events import LRR_EVENT_TYPE, LRR_EVENT_STATUS, LRR_CID_EVENT
from .events import LRR_FIRE_EVENTS, LRR_POWER_EVENTS, LRR_BYPASS_EVENTS, LRR_BATTERY_EVENTS, \
@@ -5,44 +10,60 @@ from .events import LRR_FIRE_EVENTS, LRR_POWER_EVENTS, LRR_BYPASS_EVENTS, LRR_BA


class LRRSystem(object):
"""
Handles LRR events and triggers higher-level events in the AlarmDecoder object.
"""

def __init__(self, alarmdecoder_object):
"""
Constructor

:param alarmdecoder_object: Main AlarmDecoder object
:type alarmdecoder_object: :py:class:`~alarmdecoder.AlarmDecoder`
"""
self._alarmdecoder = alarmdecoder_object

def update(self, message):
handled = False

"""
Updates the states in the primary AlarmDecoder object based on
the LRR message provided.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`
"""
# Firmware version < 2.2a.8.6
if message.version == 1:
if msg.event_type == 'ALARM_PANIC':
if message.event_type == 'ALARM_PANIC':
self._alarmdecoder._update_panic_status(True)
handled = True
elif msg.event_type == 'CANCEL':
elif message.event_type == 'CANCEL':
self._alarmdecoder._update_panic_status(False)
handled = True

# Firmware version >= 2.2a.8.6
elif message.version == 2:
source = message.event_source
if source == LRR_EVENT_TYPE.CID:
handled = self._handle_cid_message(message)
self._handle_cid_message(message)
elif source == LRR_EVENT_TYPE.DSC:
handled = self._handle_dsc_message(message)
self._handle_dsc_message(message)
elif source == LRR_EVENT_TYPE.ADEMCO:
handled = self._handle_ademco_message(message)
self._handle_ademco_message(message)
elif source == LRR_EVENT_TYPE.ALARMDECODER:
handled = self._handle_alarmdecoder_message(message)
self._handle_alarmdecoder_message(message)
elif source == LRR_EVENT_TYPE.UNKNOWN:
handled = self._handle_unknown_message(message)
self._handle_unknown_message(message)
else:
pass

return handled

def _handle_cid_message(self, message):
handled = False
"""
Handles ContactID LRR events.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`
"""
status = self._get_event_status(message)
if status is None:
print("Unknown LRR event status: {0}".format(message))
return

if message.event_code in LRR_FIRE_EVENTS:
@@ -50,7 +71,6 @@ class LRRSystem(object):
status = False

self._alarmdecoder._update_fire_status(status=status)
handled = True
if message.event_code in LRR_ALARM_EVENTS:
kwargs = {}
@@ -60,27 +80,21 @@ class LRRSystem(object):

kwargs[field_name] = int(message.event_data)
self._alarmdecoder._update_alarm_status(status=status, **kwargs)
handled = True

if message.event_code in LRR_POWER_EVENTS:
self._alarmdecoder._update_power_status(status=status)
handled = True

if message.event_code in LRR_BYPASS_EVENTS:
zone = int(message.event_data)
self._alarmdecoder._update_zone_bypass_status(status=status, zone=zone)
handled = True
self._alarmdecoder._update_zone_bypass_status(status=status, zone=int(message.event_data))

if message.event_code in LRR_BATTERY_EVENTS:
self._alarmdecoder._update_battery_status(status=status)
handled = True

if message.event_code in LRR_PANIC_EVENTS:
if message.event_code == LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER:
status = False

self._alarmdecoder._update_panic_status(status=status)
handled = True

if message.event_code in LRR_ARM_EVENTS:
# NOTE: status on OPENCLOSE messages is backwards.
@@ -93,25 +107,53 @@ class LRRSystem(object):
status = not status

self._alarmdecoder._update_armed_status(status=status, status_stay=status_stay)
handled = True


return handled

def _handle_dsc_message(self, message):
return False
"""
Handles DSC LRR events.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`
"""
pass

def _handle_ademco_message(self, message):
return False
"""
Handles ADEMCO LRR events.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`
"""
pass

def _handle_alarmdecoder_message(self, message):
return False
"""
Handles AlarmDecoder LRR events.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`
"""
pass

def _handle_unknown_message(self, message):
"""
Handles UNKNOWN LRR events.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`
"""
# TODO: Log this somewhere useful.
return False
pass

def _get_event_status(self, message):
"""
Retrieves the boolean status of an LRR message.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`

:returns: Boolean indicating whether the event was triggered or restored.
"""
status = None

if message.event_status == LRR_EVENT_STATUS.TRIGGER:


+ 3
- 0
alarmdecoder/states.py View File

@@ -1,4 +1,7 @@
class FireState:
"""
Fire alarm status
"""
NONE = 0
ALARM = 1
ACKNOWLEDGED = 2

+ 25
- 0
alarmdecoder/util.py View File

@@ -58,6 +58,15 @@ class UploadChecksumError(UploadError):


def bytes_available(device):
"""
Determines the number of bytes available for reading from an
AlarmDecoder device

:param device: the AlarmDecoder device
:type device: :py:class:`~alarmdecoder.devices.Device`

:returns: int
"""
bytes_avail = 0

if isinstance(device, alarmdecoder.devices.SerialDevice):
@@ -71,6 +80,14 @@ def bytes_available(device):
return bytes_avail

def read_firmware_file(file_path):
"""
Reads a firmware file into a dequeue for processing.

:param file_path: Path to the firmware file
:type file_path: string

:returns: deque
"""
data_queue = deque()

with open(file_path) as firmware_handle:
@@ -99,6 +116,14 @@ class Firmware(object):

@staticmethod
def read(device):
"""
Reads data from the specified device.

:param device: the AlarmDecoder device
:type device: :py:class:`~alarmdecoder.devices.Device`

:returns: string
"""
response = None
bytes_avail = bytes_available(device)



Loading…
Cancel
Save