Skip to content

Commit

Permalink
Merge pull request #8 from thanasipantazides/formatter-ping
Browse files Browse the repository at this point in the history
Add parser and collection for Formatter "I'm alive" ping
  • Loading branch information
KriSun95 authored Jan 29, 2025
2 parents 797c8db + a4e894c commit a57e104
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 2 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
__pycache__/

# Jupyter Notebook
.ipynb_checkpoints
.ipynb_checkpoints

.env/
env/
50 changes: 50 additions & 0 deletions collections/PingCollection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
Ping collection to handle the read-in Formatter ping data.
"""


class PingCollection:
"""
A container for ping data after being parsed.
Parameters
---------
parsed_data : `tuple`, length 2
Contains `error`, `data`, as returned from the parser.
old_data_time : `int`, `float`
The last time of the last data point previously extracted and
used. Default is `0` and so should take all data.
Default: 0
Example
-------
with BackwardsReader(file=self.data_file, blksize=self.buffer_size, forward=True) as f:
data = f.read_block()
error, data = Pingparser(raw_data)
ping_data = PingCollection((error, data))
plt.figure()
ping_data.plot_trace()
plt.show()
"""

def __init__(self, parsed_data, old_data_time=0):
# bring in the parsed data
self.output, self.error_flag = parsed_data

# used in the filter to only consider data with times > than this
self.last_data_time = old_data_time

def get_unixtime(self):
return self.output["unixtime"]

def get_global_errors_int(self):
return self.output["global_errors_int"]

def get_all_systems_state(self):
return {k: self.output[k]["system_state_str"] for k in self.output.keys() if type(k) is int}

def get_all_systems_error(self):
return {k: self.output[k]["system_error_str"] for k in self.output.keys() if type(k) is int}
Binary file added data/ping.log
Binary file not shown.
140 changes: 140 additions & 0 deletions parsers/Pingparser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@

"""Dictionary describing values the system_state field can take"""
system_state_values = {
"off": 0x00,
"await": 0x01,
"startup": 0x02,
"init": 0x03,
"loop": 0x04,
"end": 0x05,
"disconnect": 0x06,
"abandon": 0x07,
"invalid": 0xff
}

"""Dictionary describing values the system_error field can take"""
system_error_values = {
"reading_packet": 1 << 0,
"reading_frame": 1 << 1,
"reading_invalid": 1 << 2,
"writing_invalid": 1 << 3,
"frame_packetizing": 1 << 4,
"packet_framing": 1 << 5,
"commanding": 1 << 6,
"uplink_forwarding": 1 << 7,
"downlink_buffering": 1 << 8,
"command_lookup": 1 << 9,
"buffer_lookup": 1 << 10,
"spw_vcrc": 1 << 11,
"spw_length": 1 << 12
}

# inverting the above dictionaries, so we can look up by the `int` value and get the name
system_state_names = {v: k for k, v in system_state_values.items()}
system_error_names = {v: k for k, v in system_error_values.items()}

# debug
# pprint(system_error_names)
# pprint(system_error_values)
# assert(len(system_error_values) == len(system_error_names))



def pingparser(data: bytes):
"""
Parse Formatter Ping packet into a `dict`.
Paramaters
----------
data : `bytes`
A raw data frame output by the Formatter board. Raw data 46 bytes total, 4 bytes of
Formatter unixtime, 2 bytes of global software status (currently unused), then 10
repeating byte patterns for each onboard system. The pattern is:
- system hex ID [1 byte]
- system state code [1 byte]
- system error code [2 bytes]
The order in which systems appear here is dictated by the loop order in the Formatter
software.
Returns
-------
`tuple(dict, bool)` :
The bool indicates whether there was an error in parsing. The dict has the following
structure:
- `unixtime`: the Formatter clock value when it recorded the ping packet.
- `global_errors_int`: currently unused, always zero.
- A `dict` of `dict`s. The outer key is the system hex ID code (see systems.json),
and the inner keys are:
- `system_state_int`: raw integer code for system state (8 bit)
- `system_error_int`: raw integer code for system state (16 bit)
- `system_state_str`: string description of system state
- `system_error_str`: `dict(str, bool)` indicating which error flags are raised.
"""

frame_size = 46
frame_count = len(data) // frame_size
if frame_count > 1:
data = data[0:frame_size]

error_flag = False

if len(data) % frame_size != 0:
print("Formatter ping frames are 46 bytes long. Cannot parse from a different block size.")
error_flag = True
return {}, error_flag

unixtime_raw = int.from_bytes(data[0:4], "big")
global_errors_int = int.from_bytes(data[5:6], "big")

nsys = (len(data) - 6) // 4 # count systems in the rest of the packet
result = {"unixtime": unixtime_raw, "global_errors_int": global_errors_int}

for k in range(nsys): # k indexes systems
i = 6 + 4*k # i indexes the data array

try:
this_system = int(data[i])
this_state_int = int(data[i+1])
this_error_int = int.from_bytes(data[i+2:i+4], "big")

result[this_system] = {
"system_state_int": this_state_int,
"system_error_int": this_error_int,
"system_state_str": system_state_names[this_state_int],
"system_error_str": {system_error_names[e]: (this_error_int & e) != 0 for e in system_error_names.keys()}
}
except KeyError:
error_flag = True

return result, error_flag



if __name__ == "__main__":

import sys
from pprint import pprint

if len(sys.argv) == 1:
testfile = "data/ping.log"
with open(testfile, "rb") as f:
data = f.read()
p = pingparser(data)
pprint(p)

elif len(sys.argv) == 2:
print("opening", sys.argv[1])
with open(sys.argv[1], "rb") as f:

def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n):
yield lst[i:i + n]

data = f.read()
for chunk in chunks(data, 46):
p, e = pingparser(chunk)
pprint(p)

else:
print("use like this:\n\t> python parsers/Pingparser.py\n\t\tor\n\t>python parsers/Pingparser.py path/to/ping/file.log")
2 changes: 1 addition & 1 deletion parsers/Powerparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

def adcparser(data: bytes):
"""
Parse ADC measurements of voltage and current into a `numpy.ndarray`.
Parse ADC measurements of voltage and current into a `dict`.
Paramaters
----------
Expand Down

0 comments on commit a57e104

Please sign in to comment.