def __init__(self): # # Parser combinators # SPACES = spaces() optional_spaces = optional(SPACES) empty = SPACES.parsecmap(lambda x: EMPTY) comment = string('%%%') >> regex('.*') comment = comment.parsecmap(Comment) codepoint_hex = regex('[0-9A-F]+') codepoint_hex = codepoint_hex.parsecmap(lambda x: int(x, 16)) codepoint = string('U+') >> codepoint_hex codepoint_seq = sepBy(codepoint, SPACES) codepoint_seq = codepoint_seq.parsecmap(tuple) arrow = string('=>') arrow = optional_spaces >> arrow << optional_spaces mapping = joint( codepoint_seq << arrow, codepoint_seq, optional(comment), ) mapping = mapping.parsecmap(lambda x: Mapping(x[0], x[1], x[2])) line = try_choice(mapping, try_choice( comment, empty, )) self.parse = line.parse
def component(duty_exp: DutyExpression) -> Parser: """Matches a string prefix and returns the associated type id, along with any parsed amounts and units according to their applicability, as a 4-tuple of (id, amount, monetary unit, measurement).""" prefix = duty_exp.prefix has_amount = duty_exp.duty_amount_applicability_code has_measurement = duty_exp.measurement_unit_applicability_code has_monetary = duty_exp.monetary_unit_applicability_code id = token(prefix).result(duty_exp) this_value = if_applicable(has_amount, decimal) this_monetary_unit = if_applicable( has_monetary, spaces() >> self._monetary_unit, # We must match the percentage if the amount should be there # and no monetary unit matches. default=(percentage_unit if has_amount == ApplicabilityCode.MANDATORY else optional(percentage_unit)), ) this_measurement = if_applicable( has_measurement, optional(token("/")) >> self._measurement, ) component = joint(id, this_value, this_monetary_unit, this_measurement) measurement_only = joint(id, this_measurement).parsecmap( lambda t: (t[0], None, None, t[1]), ) # It's possible for units that contain numbers (e.g. DTN => '100 kg') # to be confused with a simple specific duty (e.g 100.0 + kg) # So in the case that amounts are only optional and measurements are present, # we have to check for just measurements first. return (measurement_only ^ component if has_amount == ApplicabilityCode.PERMITTED and has_measurement != ApplicabilityCode.NOT_PERMITTED else component).parsecmap( lambda exp: component_output( duty_expression=exp[0], duty_amount=exp[1], monetary_unit=exp[2], component_measurement=exp[3], ), )
def measurement(m: Measurement) -> Parser: """ For measurement units and qualifiers, we match a human-readable version of the unit to its internal code. Units by themselves are always allowed, but only some combinations of units and qualifiers are permitted. """ unit = abbrev(m.measurement_unit) if m.measurement_unit_qualifier: qualifier = token("/") >> abbrev(m.measurement_unit_qualifier) else: qualifier = empty return joint(unit, qualifier).result(m)
def __init__(self, header): header_fields = defaultdict(str) for line in header.splitlines(): field, label = line[:60], trim_whitespace(line[60:]) header_fields[label] += field self.header_fields = dict(header_fields) self.version, self.type, satellite = p.Parser( p.joint( n_ANY(9).parsecmap(float), p.compose(n_ANY(11), p.one_of('MON')), p.compose(n_ANY(19), n_ANY(1)))).parse( self.header_fields['RINEX VERSION / TYPE'])
from base import pid from parsing import * from parsec import joint Gyro = pid(0x10) >> \ joint( field('X', int16), field('Y', int16), field('Z', int16), field('Temperature', int16) ).bind(to_dict) Gyro >>= label_as('Gyro')
from parsec import joint from parsing import * minutes = packed('<B').parsecmap(lambda x: timedelta(minutes=x)) array200 = packed('<200s') Message = joint( field('Interval', minutes), field('RepeatCount', byte), field('Message', array200) ).bind(to_dict) Message >>= label_as('Periodic Message')
def __init__( self, duty_expressions: Iterable[DutyExpression], monetary_units: Iterable[MonetaryUnit], permitted_measurements: Iterable[Measurement], component_output: Type[TrackedModel] = MeasureComponent, ): # Decimal numbers are a sequence of digits (without a left-trailing zero) # followed optionally by a decimal point and a number of digits (we have seen # some percentage values have three decimal digits). Money values are similar # but only 2 digits are allowed after the decimal. # TODO: work out if float will cause representation problems. decimal = regex(r"(0|[1-9][0-9]*)([.][0-9]+)?").parsecmap(float) # Specific duty amounts reference various types of unit. # For monetary units, the expression just contains the same code as is # present in the sentence. Percentage values correspond to no unit. self._monetary_unit = (reduce(try_choice, map(code, monetary_units)) if monetary_units else fail) percentage_unit = token("%").result(None) # We have to try and parse measurements with qualifiers first # else we may match the first part of a unit without the qualifier with_qualifier = [ m for m in permitted_measurements if m.measurement_unit_qualifier is not None ] no_qualifier = [ m for m in permitted_measurements if m.measurement_unit_qualifier is None ] measurements = [ measurement(m) for m in chain(with_qualifier, no_qualifier) ] self._measurement = reduce(try_choice, measurements) if measurements else fail # Each measure component can have an amount, monetary unit and measurement. # Which expression elements are allowed in a component is controlled by # the duty epxression applicability codes. We convert the duty expressions # into parsers that will only parse the elements that are permitted for this type. def component(duty_exp: DutyExpression) -> Parser: """Matches a string prefix and returns the associated type id, along with any parsed amounts and units according to their applicability, as a 4-tuple of (id, amount, monetary unit, measurement).""" prefix = duty_exp.prefix has_amount = duty_exp.duty_amount_applicability_code has_measurement = duty_exp.measurement_unit_applicability_code has_monetary = duty_exp.monetary_unit_applicability_code id = token(prefix).result(duty_exp) this_value = if_applicable(has_amount, decimal) this_monetary_unit = if_applicable( has_monetary, spaces() >> self._monetary_unit, # We must match the percentage if the amount should be there # and no monetary unit matches. default=(percentage_unit if has_amount == ApplicabilityCode.MANDATORY else optional(percentage_unit)), ) this_measurement = if_applicable( has_measurement, optional(token("/")) >> self._measurement, ) component = joint(id, this_value, this_monetary_unit, this_measurement) measurement_only = joint(id, this_measurement).parsecmap( lambda t: (t[0], None, None, t[1]), ) # It's possible for units that contain numbers (e.g. DTN => '100 kg') # to be confused with a simple specific duty (e.g 100.0 + kg) # So in the case that amounts are only optional and measurements are present, # we have to check for just measurements first. return (measurement_only ^ component if has_amount == ApplicabilityCode.PERMITTED and has_measurement != ApplicabilityCode.NOT_PERMITTED else component).parsecmap( lambda exp: component_output( duty_expression=exp[0], duty_amount=exp[1], monetary_unit=exp[2], component_measurement=exp[3], ), ) # Duty sentences can only be of a finite length – each expression may only # appear once and in order of increasing expression id. So we try all expressions # in order and filter out the None results for ones that did not match. expressions = ([ component(exp) ^ empty for exp in sorted(duty_expressions, key=lambda e: e.sid) ] if duty_expressions else [fail]) self._sentence = joint(*expressions).parsecmap( lambda sentence: [exp for exp in sentence if exp is not None], )
from parsec import joint from base import pid, count from parsing import * RadFET = pid(0x35) >> joint(field('Status', byte), field( 'Temperature', uint32), field('Voltages', count(uint32, 3))).bind(to_dict) RadFET >>= label_as('RadFET')
from parsec import joint from base import pid, count from parsing import * ExperimentalSunSStatus = joint( field('ALS_ACK', uint16), field('ALS_Presence', uint16), field('ALS_ADC_Valid', uint16), ) ExperimentalSunSStatus >>= to_dict SingleALS = count(uint16, 4) ALSs = count(SingleALS, 3) Temperatures = joint( field('Structure', uint16), field('A', uint16), field('B', uint16), field('C', uint16), field('D', uint16), ).bind(to_dict) ExperimentalSunSPrimary = pid(0x11) \ >> joint( field('WhoAmI ', byte), field('Status', ExperimentalSunSStatus), field('VisibleLight', ALSs), field('Temperatures', Temperatures), ).bind(to_dict) ExperimentalSunSPrimary >>= label_as('ExpSunS.Primary')
from base import pid from parsing import * from parsec import joint Sail = pid(0x18) >> \ joint( field('Temperature', uint16), field('Open', boolean) ).bind(to_dict) Sail >>= label_as('Sail')
from base import pid, label_as, field, to_dict from parsing import byte, uint16 from parsec import Parser, Value from emulator.beacon_parser import eps_controller_a_telemetry_parser, eps_controller_b_telemetry_parser, \ error_counting_telemetry, experiment_telemetry_parser, mcu_temperature_parser from emulator.beacon_parser.parser import BitArrayParser from math import ceil PayloadWhoAmI = pid(0x30) >> count(byte, 1) PayloadWhoAmI >>= label_as('Payload Who Am I') PayloadHousekeeping = pid(0x34) >> joint( field('INT 3V3D', uint16), field('OBC 3V3D', uint16), ).bind(to_dict) PayloadHousekeeping >>= label_as('Payload Housekeeping') ########################################################################## class PayloadOBCTelemetryParser: def __init__(self): self.storage = {} def write(self, category, name, value): if category not in self.storage.keys(): self.storage[category] = {} self.storage[category][name] = value
from parsec import spaces from parsec import sepBy from parsec import sepBy1 logger = logging.getLogger(__name__) optionalspaces = optional(spaces()) arrow = optionalspaces >> string('->') << optionalspaces identifier = (regex('[a-zA-Z_$][a-zA-Z_$0-9]*') ^ string('<init>') ^ string('<clinit>')) className = sepBy1(identifier, string('$')) packagedFullName = sepBy1(identifier, string('.')) packagedClassName = packagedFullName.parsecmap(lambda l: '.'.join(l)) typeName = packagedClassName | regex('[a-z]+') javatype = joint(typeName, optional(string('[]'))) methodName = identifier methodArguments = sepBy(optionalspaces >> javatype << optionalspaces, string(',')) methodArguments = string('(') >> methodArguments << string(')') linenumber = regex('[0-9]+').parsecmap(lambda s: int(s)) linenumbers = joint( linenumber << string(':'), linenumber << string(':'), ) member = joint( optional(linenumbers), javatype << spaces(),
from parsec import joint from parsing import * CounterConfig = joint( field('Limit', byte), field('Increment', byte), field('Decrement', byte), field('Zero', byte), ).bind(to_dict) CounterConfig >>= label_as('Counter Config') ErrorCounters = joint(field('Comm', CounterConfig), field('Eps', CounterConfig), field('RTC', CounterConfig), field('Imtq', CounterConfig), field('N25q Flash 1', CounterConfig), field('N25q Flash 2', CounterConfig), field('N25q Flash 3', CounterConfig), field('N25q TMR', CounterConfig), field('FRAM TMR', CounterConfig), field('Payload', CounterConfig), field('Camera', CounterConfig), field('Suns', CounterConfig), field('Antenna Primary', CounterConfig), field('Antenna Backup', CounterConfig)).bind(to_dict) ErrorCounters >>= label_as('Error Counters')
from parsec import joint from parsing import * time = packed('<Q').parsecmap(lambda x: timedelta(milliseconds=x)) TimeCorrection = joint(field('Internal Clock Weight', int16), field('External Clock Weight', int16)).bind(to_dict) TimeCorrection >>= label_as('Time Correction') MissionTime = joint(field('Internal Time', time), field('External Time', time)).bind(to_dict) MissionTime >>= label_as('Mission Time')
from parsec import joint, count from adcs import * from antenna import * from error_counters import * from message import * from sail import * from time import * def flat(values): result = {} for v in values: key = v.keys()[0] result[key] = v[key] return result PersistentStateParser = joint(AntennaConfiguration, MissionTime, TimeCorrection, SailState, ErrorCounters, AdcsConfiguration, Message).parsecmap(flat) __all__ = ['PersistentStateParser']