-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from thanasipantazides/formatter-ping
Add parser and collection for Formatter "I'm alive" ping
- Loading branch information
Showing
5 changed files
with
195 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,4 +3,7 @@ | |
__pycache__/ | ||
|
||
# Jupyter Notebook | ||
.ipynb_checkpoints | ||
.ipynb_checkpoints | ||
|
||
.env/ | ||
env/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters