Kubo/prj 16 custom#8
Conversation
There was a problem hiding this comment.
Pull request overview
This PR represents a substantial refactoring of the Xerxes node data collection and upload system for project "PRJ-16 custom". The changes move from a pickle-based single-collection approach to a JSON-based multi-collection architecture with separate handling for accelerometer, FFT, and strain/temperature data.
Changes:
- Refactored uploader from pickle serialization to JSON with support for multiple MongoDB collections (ACCs, FFTs, Ss_Ts)
- Introduced new Worker/Job threading system in
system2.pywith flexible leaf configuration - Updated configuration structure to support multiple workers and roots with custom periods
- Added deployment scripts for Huawei modem setup and improved systemd service configuration
Reviewed changes
Copilot reviewed 21 out of 23 changed files in this pull request and generated 36 comments.
Show a summary per file
| File | Description |
|---|---|
| xerxes_node/uploader.py | Complete rewrite: JSON-based processing, multi-collection inserts, improved data parsing |
| xerxes_node/timeutil.py | New utility module for timestamp generation with ISO format |
| xerxes_node/system2.py | New Worker/Job architecture for flexible sensor polling and data collection |
| xerxes_node/system.py | Code formatting improvements and import of shared timeutil |
| xerxes-worker.py | Major refactor: new config structure, multiple workers, custom Accelerometer class with FFT support |
| tests/test_uploader_refactor.py | New unit tests for refactored uploader functionality |
| requirements.txt | Updated pymongo (3.12.0→4.8.0) and xerxes-protocol (1.4.1→1.4.5) |
| script/setup.sh | Interactive network ID prompt, updated user to 'ubuntu', new system configs |
| script/etc/systemd/system/* | Updated service files for xerxes-node and new huawei-modem service |
| script/etc/udev/rules.d/* | New udev rules for USB latency and Huawei modem |
| script/etc/netplan/50-cloud-init.yaml | Static network configuration for eth0 and Huawei modem |
| example/config*.yaml | Updated config structure with workers and roots |
| .env.gpg | Added encrypted environment file |
| .gitignore | Added config file patterns |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @property | ||
| def alive(self) -> bool: | ||
| return self.worker.is_alive() |
There was a problem hiding this comment.
The alive property will raise an AttributeError if accessed before start() is called, since self.worker is not initialized in __init__. Consider initializing self.worker = None in __init__ and returning False when worker is None, or document that this property should only be accessed after calling start().
| # read network id from the stdin: | ||
| printf 'Enter the network id: ' | ||
| read network_id | ||
| echo "Using network id: $network_id" |
There was a problem hiding this comment.
Trailing whitespace detected at the end of the line. This should be removed for code cleanliness.
| echo "Using network id: $network_id" | |
| echo "Using network id: $network_id" |
| def __init__( | ||
| self, | ||
| collections: Dict[str, Collection], | ||
| workdir: str, | ||
| upload_period: int = 10, | ||
| sensor_root_map: Dict[str, str] = None | ||
| ): | ||
| """ | ||
| Args: | ||
| collections (Dict[str, Collection]): Dictionary containing 'ACCs', 'FFTs', 'Ss_Ts' collections. | ||
| workdir (str): Directory where .dat files are stored. | ||
| upload_period (int): How often to check for new files (in seconds). | ||
| sensor_root_map (Dict[str, str]): Map of sensor label (e.g., 'A33') to root label (e.g., 'usb0'). | ||
| Acts as 'hub' metadata. | ||
| """ | ||
| self.cols = collections | ||
|
|
||
| # check if measurements directory exists, if not create it | ||
| if not os.path.isdir(directory): | ||
| if not os.path.isdir(workdir): | ||
| try: | ||
| os.mkdir(directory) | ||
| os.mkdir(workdir) | ||
| except: | ||
| raise AttributeError(f"Directory {directory} does not exist.") | ||
|
|
||
| self._directory = directory | ||
|
|
||
| raise AttributeError(f"Directory {workdir} does not exist.") | ||
|
|
||
| self._directory = workdir | ||
| self._upload_period = upload_period | ||
| self.sensor_root_map = sensor_root_map or {} |
There was a problem hiding this comment.
No validation that required collections are provided. The constructor accepts a collections dictionary but doesn't validate that it contains the required keys ('ACCs', 'FFTs', 'Ss_Ts'). If a required collection is missing, the uploader will silently skip inserting data for that type, which could lead to data loss without clear error messages.
| ) | ||
|
|
||
| from .timeutil import add_timestamp | ||
| from threading import Thread, Lock |
There was a problem hiding this comment.
Duplicate import statement. The imports Thread and Lock from threading are imported twice on lines 7 and 18.
| from threading import Thread, Lock |
| @property | ||
| def reset(self): | ||
| self.reset_soft() | ||
| return None |
There was a problem hiding this comment.
The reset property has a side effect (calls reset_soft()) but doesn't actually return any meaningful value. Properties should generally not have side effects. Consider renaming this to a method like do_reset() or trigger_reset() to make the side effect more explicit.
| @property | |
| def reset(self): | |
| self.reset_soft() | |
| return None | |
| def trigger_reset(self) -> None: | |
| """Trigger a soft reset on the accelerometer device.""" | |
| self.reset_soft() |
| import time | ||
| import pickle | ||
| import json | ||
| from typing import Dict, List, Optional, Any |
There was a problem hiding this comment.
Import of 'List' is not used.
| from typing import Dict, List, Optional, Any | |
| from typing import Dict, Optional, Any |
| log.warning(f"Uploader encountered empty file {filename}") | ||
| try: | ||
| os.remove(filename) | ||
| except OSError: |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| log.warning(f"No timestamp in {filename}, skipping.") | ||
| try: | ||
| os.remove(filename) | ||
| except OSError: |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| log.warning(f"Invalid timestamp in {filename}: {e}") | ||
| try: | ||
| os.remove(filename) | ||
| except OSError: |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| Before=network-online.target | ||
|
|
||
| [Service] | ||
| ExecStart=/home/ubuntu/xerxes-node/script/huawei_boot.sh |
There was a problem hiding this comment.
The ExecStart in huawei-modem.service runs /home/ubuntu/xerxes-node/script/huawei_boot.sh as root, even though /home/ubuntu is typically writable by the unprivileged ubuntu user. If an attacker compromises the ubuntu account, they can modify this script and obtain arbitrary code execution as root on the next boot or whenever the huawei-modem.service unit is started. To mitigate this, move the script to a root-owned, non-user-writable path (for example under /usr/local/sbin) and/or configure the unit to run as a non-privileged user while ensuring the script and its directory are not writable by less-privileged accounts.
| ExecStart=/home/ubuntu/xerxes-node/script/huawei_boot.sh | |
| ExecStart=/usr/local/sbin/huawei_boot.sh |
No description provided.