21 from warnings
import warn
27 """! Mirror ID numbers from base/sources.h"""
67 SLCHAN_LOG_INFO = 0x7D
69 SLCHAN_LOG_WARN = 0x7E
77 Represent messages stored by the Logger program and generated by devices
78 with compatible firmware.
80 Messages are 4 element MessagePacked arrays, with the first element a
81 constant 0x55, second and third elements identifying the source and message
82 IDs and the final element containing the data for that message.
86 __slots__ = [
"SourceID",
"ChannelID",
"Data"]
90 Create message with specified source, channel and data.
92 Note that the C library and utilities only support a limited range of
93 types within these messages. Check the library documentation for
96 @param sourceID Message Source
97 @param channelID Message Channel
98 @param data Message value / data
100 @sa library/base/sources.h
103 assert 0 <= sourceID
and sourceID < 128
104 assert 0 <= channelID
and channelID < 128
107 f
"Invalid source/channel values: Source: {sourceID}, Channel: {channelID}, Data: {data}"
109 raise ValueError(
"Invalid source/channel values")
121 Unpack raw messagepack bytes or list into SLMessage.
122 If a list is provided, it's expected to correspond to each member of
123 the raw array, i.e. [0x55, sourceID, channelID, data]
125 @param cl Class to be created (classmethod)
126 @param data Raw bytes or a list of values.
127 @returns New message instance
129 if isinstance(data, bytes):
130 data = msgpack.unpackb(data)
131 if not isinstance(data, list)
or len(data) != 4:
132 raise ValueError(
"Bad message")
133 assert data[0] == 0x55
134 return cl(data[1], data[2], data[3])
138 Return packed binary representation of message
139 @returns Bytes representing message in messagepack form
145 Represent class as packed binary data
146 @todo Replace with standards compliant repr
147 @returns Message, as bytes
149 return self.
packpack()
152 """! @returns printable representation of message"""
153 return f
"{self.SourceID:02x}\t{self.ChannelID:02x}\t{str(self.Data)}"
158 Software message source
160 Provide a framework for creating valid messages from within Python code.
161 Although this class enforces channel names and provides support for the
162 standard messages (except timestamps), no restriction is placed on the data
163 types used in data messages. See separate documentation for the library to
166 @sa library/base/sources.h
170 self, sourceID, name="PythonDL", dataChannels=1, dataChannelNames=None
173 Any valid source must have a source ID, name and a list of named data channels
174 @param sourceID Valid data source ID number - @see IDs
175 @param name Source Name (Default: PythonDL)
176 @param dataChannels Number of data channels to be created (ID 3+)
177 @param dataChannelNames Names for data channels (First entry = Channel 3)
184 self.
ChannelMapChannelMap = [
"Name",
"Channels",
"Timestamp"]
187 raise ValueError(
"Invalid Source ID")
189 if dataChannelNames
is None:
190 dataChannelNames = [f
"Data{x+1}" for x
in range(dataChannels)]
192 if len(dataChannelNames) != dataChannels:
194 f
"Inconsistent number of channels specified: Expected {dataChannels}, got {len(dataChannels)}"
197 if len(dataChannelNames) > 0:
198 self.
ChannelMapChannelMap.extend(dataChannelNames)
201 """! @returns Source name message (Channel 0)"""
205 """! @returns Channel name map message (Channel 1)"""
210 @returns INFO level log message
211 @param message Message text
217 @returns WARNING level log message
218 @param message Message text
224 @returns ERROR level log message
225 @param message Message text
231 Placeholder for timestamp message (Channel 2)
233 Sources should provide a timestamp periodically to allow messages
234 generated at a particular time to be grouped.
237 @returns N/A - Throws NotImplemented exception
243 @returns Message representing data from this source
244 @param channelID Channel ID (Must correspond to a map entry)
245 @param data Message data
247 assert channelID < len(self.
ChannelMapChannelMap)
253 Source and channel map data
257 __slots__ = [
"_s",
"_log"]
260 """! Represent sources being tracked"""
263 __slots__ = [
"id",
"name",
"channels",
"lastTimestamp"]
265 def __init__(self, number, name=None, channels=None, lastTimestamp=None):
267 Tracked source must be identified by name
268 @param number SourceID
269 @param name Source Name
270 @param channels Channel Map
271 @param lastTimestamp Last source timestamp received
277 self.
namename = f
"[{number:02x}]"
278 self.
namename = str(name)
284 self.
channelschannels = [
"Name",
"Channels",
"Timestamp"]
295 Support subscripted access to channel names
297 @returns String representing channel name
300 return str(self.
channelschannels[ch])
312 Allow iteration over channel list, e.g.
314 for channel in source:
317 @returns Iterator over channel list
323 Represent source by its name
329 """! Represents a single channel"""
336 @param name Channel Name
343 @todo Replace with more python compliant function
344 @returns Channel name
351 """! Initialise blank map"""
355 self.
_log_log = logging.getLogger(__name__)
359 Support subscripted access to sources
361 @returns Source object
363 if not isinstance(ix, int):
368 """! @returns Iterator over sources"""
369 return iter(self.
_s_s)
372 """! @returns next() source"""
373 return next(self.
_s_s)
376 """! @returns Dictionary representation of map"""
377 return {x: self.
_s_s[x].channels
for x
in self.
_s_s}
381 @param source Source ID
382 @returns Formatted name for source ID
385 if isinstance(source, str):
386 source = int(source, base=0)
389 except Exception
as e:
390 self.
_log_log.error(str(e))
391 if source
in self.
_s_s:
392 return self.
_s_s[source].name
394 return f
"[0x{source:02x}]"
398 @returns Formatted channel name
399 @param source Source ID
400 @param channel Channel ID
403 if isinstance(source, str):
404 channel = int(channel, base=0)
406 channel = int(channel)
407 except Exception
as e:
408 self.
_log_log.error(str(e))
411 if channel < len(self.
_s_s[source].channels):
412 return self.
_s_s[source].channels[channel]
420 return f
"[0x{channel:02x}]"
424 Create or update source
425 @param source Source ID
426 @param name Source Name
431 f
"Source 0x{source:02x} already exists (as {self.GetSourceName(source)})"
433 self.
_s_s[source] = self.
SourceSource(source, name)
437 @param source Source ID
438 @returns True if source already known
440 return source
in self.
_s_s
444 Special cases default channels that must always exist (SLCHAN_NAME,
445 SLCHAN_MAP, SLCHAN_TSTAMP, SLCHAN_LOG_INFO, SLCHAN_LOG_WARN,
446 SLCHAN_LOG_ERR), then checks for the existence of others.
448 @param source Source ID
449 @param channel Channel ID
450 @returns True if channel exists in specified source
452 if channel
in [0, 1, 2, 125, 126, 127]:
456 return channel < len(self.
_s_s[source].channels)
461 Update source name, creating source if required.
462 @param source SourceID
463 @param name SourceName
469 self.
_s_s[source].name = name
473 Update channel map for a source, creating if required
474 @param source SourceID
475 @param channels List of channel names
481 self.
_s_s[source].channels = channels
485 Update last timestamp value for a source, creating source if required.
486 @param source Source ID
487 @param timestamp Timestamp value
492 self.
_s_s[source].lastTimestamp = int(timestamp)
497 Parse incoming messages.
499 Maintains an internal mapping of channel and source names to provide prettier output.
501 Expects data to be provided one message at a time to .Process(), and will
502 return data as a dict, printable string or as an SLMessage().
507 Initialise message sink
509 Optionally accepts a logging object for any error, warning, or
510 information messages encountered in the source data.
511 @param msglogger Logger object for messages extracted from data
517 self.
_log_log = logging.getLogger(__name__)
518 logging.addLevelName(5,
"DDebug")
523 if self.
_msglog_msglog
is None:
524 self.
_log_log.warn(
"No message logger specified")
525 self.
_msglog_msglog = logging.getLogger(f
"{__name__}.msg")
528 """! @returns Source/Channel map"""
533 Pretty print a message
534 @param msg Message object
535 @returns Formatted Message as string
537 return f
"{self._sm.GetSourceName(msg.SourceID)}\t{self._sm.GetChannelName(msg.SourceID, msg.ChannelID)}\t{msg.Data}"
539 def Process(self, message, output="dict", allMessages=False):
541 Process an incoming message
543 Accepts SLMessage object or bytes that can be unpacked into one.
545 If a valid message is decoded, internal data structures are updated to
546 provide channel and source names for message formatting and tracking of
547 the last timestamp seen from each source. Any data messages are
548 returned as a string or as a dictionary.
550 @param message Message (or bytes) to be decoded
551 @param output Output format for each message: String, dict, raw (SLMessage)
552 @param allMessages Output messages normally used internally
553 @return Message in selected format
555 if output
not in [
None,
"string",
"dict",
"raw"]:
556 raise ValueError(
"Bad output type")
558 if not isinstance(message, SLMessage):
560 message = SLMessage.unpack(message)
562 self.
_log_log.warning(
"Bad message encountered, skipping")
563 self.
_log_log.debug(repr(message))
566 if not self.
_sm_sm.SourceExists(message.SourceID):
567 self.
_sm_sm.NewSource(message.SourceID)
569 suppressOutput =
False
570 if message.ChannelID == 0:
573 f
"New name for {self._sm.GetSourceName(message.SourceID)}: {message.Data}",
575 self.
_sm_sm.SetSourceName(message.SourceID, message.Data)
576 suppressOutput =
True
577 elif message.ChannelID == 1:
580 f
"New channels for {self._sm.GetSourceName(message.SourceID)}: {message.Data}",
582 self.
_sm_sm.SetChannelNames(message.SourceID, message.Data)
583 suppressOutput =
True
584 elif message.ChannelID == 2:
587 f
"New update time for {self._sm.GetSourceName(message.SourceID)}: {message.Data}",
589 self.
_sm_sm.UpdateTimestamp(message.SourceID, message.Data)
590 suppressOutput =
True
591 elif message.ChannelID == 125:
593 suppressOutput =
True
594 elif message.ChannelID == 126:
596 suppressOutput =
True
597 elif message.ChannelID == 127:
599 suppressOutput =
True
601 if allMessages
or (
not suppressOutput):
604 "sourceID": message.SourceID,
605 "sourceName": self.
_sm_sm.GetSourceName(message.SourceID),
606 "channelID": message.ChannelID,
607 "channelName": self.
_sm_sm.GetChannelName(
608 message.SourceID, message.ChannelID
610 "data": message.Data,
612 elif output ==
"string":
614 elif output ==
"raw":
Mirror ID numbers from base/sources.h.
Represents a single channel.
Represent sources being tracked.
def __str__(self)
Represent source by its name.
def __init__(self, number, name=None, channels=None, lastTimestamp=None)
Tracked source must be identified by name.
def __getitem__(self, ch)
Support subscripted access to channel names.
def __iter__(self)
Allow iteration over channel list, e.g.
lastTimestamp
Last timestamp received.
channels
List of channels.
Source and channel map data.
def SetChannelNames(self, source, channels)
Update channel map for a source, creating if required.
def GetSourceName(self, source)
def NewSource(self, source, name=None)
Create or update source.
def SetSourceName(self, source, name)
Update source name, creating source if required.
def ChannelExists(self, source, channel)
Special cases default channels that must always exist (SLCHAN_NAME, SLCHAN_MAP, SLCHAN_TSTAMP,...
_s
Dictionary of sources, keyed by ID.
def UpdateTimestamp(self, source, timestamp)
Update last timestamp value for a source, creating source if required.
def SourceExists(self, source)
def __init__(self)
Initialise blank map.
def GetChannelName(self, source, channel)
_log
Logger object for later use.
def __getitem__(self, ix)
Support subscripted access to sources.
def __init__(self, msglogger=None)
Initialise message sink.
def FormatMessage(self, msg)
Pretty print a message.
def Process(self, message, output="dict", allMessages=False)
Process an incoming message.
_log
General logging object.
_sm
Channel Map to be filled from input data.
_msglog
Logger for messages extracted from data.
def InfoMessage(self, message)
def ErrorMessage(self, message)
ChannelMap
Channel Map: Names for all data channels.
def ChannelsMessage(self)
SourceID
ID number for this source -.
def __init__(self, sourceID, name="PythonDL", dataChannels=1, dataChannelNames=None)
Any valid source must have a source ID, name and a list of named data channels.
def TimestampMessage(self)
Placeholder for timestamp message (Channel 2)
def DataMessage(self, channelID, data)
def WarningMessage(self, message)
Python representation of a logged message.
ChannelID
Message Channel.
Data
Message value / embedded data.
def __repr__(self)
Represent class as packed binary data.
def __init__(self, sourceID, channelID, data)
Create message with specified source, channel and data.
def pack(self)
Return packed binary representation of message.
def unpack(cl, data)
Unpack raw messagepack bytes or list into SLMessage.