Browse Source

Updated timestamp & added readme

xnecas 6 giờ trước cách đây
mục cha
commit
4907d675d2
6 tập tin đã thay đổi với 276 bổ sung6 xóa
  1. 15 0
      LICENSE
  2. 54 0
      README.human
  3. 187 4
      README.md
  4. 1 1
      examples/example_bridge.py
  5. BIN
      nbus_api/__pycache__/nbus_bridge.cpython-313.pyc
  6. 19 1
      nbus_api/nbus_bridge.py

+ 15 - 0
LICENSE

@@ -0,0 +1,15 @@
+Copyright 2025 Matúš Nečas
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the “Software”), to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.

+ 54 - 0
README.human

@@ -0,0 +1,54 @@
+This code is human-written.
+Not perfect, not pure — but every line carries the echo of a man who tried:
+a true story of who I was,
+and who I wanted to be.
+
+This code wasn’t crafted on a delightful terrace.
+It was forged in the quiet hours —
+through crimson tides of mistakes,
+through sunsets when even hope felt out of reach.
+And still I kept writing,
+as if each line could pull me closer to something that mattered.
+
+It’s a machine, yes —
+but more like a lone Formula 1 phantom on a rain-soaked track:
+meant to run, meant to endure,
+meant to survive the pit stops where you question everything.
+
+Runnable.
+Repairable.
+Built to finish the race.
+
+To me, it stands as a reward for caring about the hidden details —
+the marbles in the margins,
+the silent voices no one will ever hear.
+The kind of beauty only its creator ever notices,
+and only in the quiet.
+
+But we have reached the gates of the Artificial Age,
+washing the passion away — not only from silicon scripts,
+but from the people who flash them.
+Creators turn into operators,
+thinkers into copyists,
+engineers into extensions of someone else’s shadow.
+
+The code stays velvet-warm on the surface,
+yet lacks the inner structure meant to last for decades.
+
+And it’s painfully tempting to walk that treacherous path —
+to become a programming zombie, marching through borrowed thoughts,
+fast, efficient, and hollow inside.
+
+But I refuse.
+
+I won’t trade consciousness for speed.
+I won’t lose my voice to the noise.
+I won’t let the work of code
+collapse into mere instructions.
+
+I want to see every line I write.
+I want to remember why I began.
+I want to be alive —
+even when the world forgets what that means...
+
+- Matúš

+ 187 - 4
README.md

@@ -1,5 +1,188 @@
-# nBus Client API 
+# nBus Client API
 
-This project provides a Python client API for the nBus protocol over a serial port. 
-It offers a high-level, strictly object-oriented approach, ensuring type safety, clear error handling
-and maintainability. It uses The Beatype runtime typechecking.
+The **nBus Client API** is a Python library providing a high-level, strictly object-oriented interface for communicating with devices over the **nBus protocol**.
+
+It focuses on:
+
+- Clean architecture  
+- Strong runtime type safety (via *Beatype*)  
+- Predictable error handling  
+- Maintainable abstractions for multi-sensor systems  
+
+The API exposes **three levels of control**:
+
+1. **`NBusSensor`** – individual sensors  
+2. **`NBusSlaveModule`** – modules containing multiple sensors  
+3. **`NBusBridge`** – top-level interface for managing modules and data streams
+
+## Serial Configuration
+
+All communication begins by creating an `NBusSerialPort` with a configuration object:
+
+```python
+from nbus_hal.nbus_serial.serial_config import *
+
+config = {
+    "port_name": "COM4",
+    "baud": NBusBaudrate.SPEED_921600,
+    "parity": NBusParity.NONE,
+    "timeout": 1.0,
+    "flush_delay": 0.05,
+    "request_attempts": 1,
+    "enable_log": False
+}
+
+port = NBusSerialPort(NBusSerialConfig(**config))
+```
+
+---
+
+# 1. Using the NBusBridge
+
+The **Bridge** represents the top-level interface for the entire nBus network.  
+It discovers modules, retrieves global sensor frames, and enables continuous streaming.
+
+### Example
+
+```python
+import time
+from nbus_hal.nbus_serial.serial_port import *
+from nbus_api.nbus_bridge import NBusBridge
+
+BRIDGE_ACQUIRE_WAIT = 0.5
+BRIDGE_STREAM_INTERVAL = 5.0
+
+port = NBusSerialPort(NBusSerialConfig(**config))
+bridge = NBusBridge(port, BRIDGE_ACQUIRE_WAIT)
+
+try:
+    bridge.init()
+
+    # --- Bridge GET commands ---
+    print("INFO:", bridge.cmd_get_info())
+    print("SLAVES:", bridge.cmd_get_slaves())
+    print("FORMAT:", bridge.cmd_get_format())
+    print("DATA:", bridge.cmd_get_data())
+
+    # --- Bridge SET commands ---
+    print("RESET:", bridge.cmd_set_reset())
+
+    # --- Streaming example ---
+    print(f"START STREAM FOR {BRIDGE_STREAM_INTERVAL} SECONDS")
+    bridge.start_streaming()
+    time.sleep(BRIDGE_STREAM_INTERVAL)
+
+    print("STREAM CHUNK:", bridge.fetch_stream_chunk())
+
+    print(f"STOP STREAM")
+    bridge.stop_streaming()
+
+    print("SAVE FULL STREAM")
+    df = bridge.fetch_full_stream()
+    df.to_csv("data.csv", index=False)
+
+finally:
+    port.flush()
+    port.close()
+```
+
+---
+
+# 2. Using the NBusSlaveModule
+
+A **Module** manages multiple sensors.
+
+### Example
+
+```python
+from nbus_api.nbus_module_slave import NBusSlaveModule
+from nbus_types.nbus_parameter_type import NBusParameterID
+
+port = NBusSerialPort(NBusSerialConfig(**config))
+module = NBusSlaveModule(port, 5)
+
+try:
+    module.init(False)
+
+    devices = module.get_devices()
+    accelerometer = devices[1]
+    led = devices[129]
+
+    print(module.cmd_get_echo(b"Hello world!"))
+    print(module.cmd_get_param(NBusParameterID.PARAM_SAMPLERATE))
+    print(module.cmd_get_all_params())
+    print(module.cmd_get_sensor_cnt())
+    print(module.cmd_get_sensor_type())
+    print(module.cmd_get_info())
+    print(module.cmd_get_format())
+    print(module.cmd_get_data())
+
+    module.cmd_set_module_stop()
+    module.cmd_set_module_start()
+
+    module.cmd_set_param(NBusParameterID.PARAM_SAMPLERATE, 12345)
+
+    params = {
+        NBusParameterID.PARAM_RANGE: 12,
+        NBusParameterID.PARAM_RANGE0: 4234
+    }
+    module.cmd_set_multi_params(params)
+
+    module.cmd_set_calibrate()
+    module.cmd_set_data({129: [1], 130: [10, -32]})
+
+finally:
+    port.flush()
+    port.close()
+```
+
+---
+
+# 3. Using NBusSensor
+
+### Example
+
+```python
+accelerometer = devices[1]
+print(accelerometer.cmd_get_param(NBusParameterID.PARAM_SAMPLERATE))
+print(accelerometer.cmd_get_all_params())
+print(accelerometer.cmd_get_sensor_type())
+print(accelerometer.cmd_get_format())
+print(accelerometer.cmd_get_data())
+
+led = devices[129]
+led.cmd_set_find(True)
+led.cmd_set_param(NBusParameterID.PARAM_SAMPLERATE, 23456)
+led.cmd_set_multi_params({
+    NBusParameterID.PARAM_RANGE: 12,
+    NBusParameterID.PARAM_RANGE0: 4234
+})
+led.cmd_set_calibrate()
+led.cmd_set_data([1])
+print(led.cmd_get_data())
+```
+
+---
+
+# API Architecture Overview
+
+```
+┌──────────────────────────┐
+│       NBusBridge         │
+└──────────────┬───────────┘
+               │ 2..*
+┌──────────────▼───────────┐
+│    NBusSlaveModule       │
+└──────────────┬───────────┘
+               │ 1..*
+┌──────────────▼───────────┐
+│        NBusSensor        │
+└──────────────────────────┘
+```
+
+# License
+
+MIT License
+
+# Author
+[*This code is human-written*](README.human) by Matúš Nečas, 2024-2025

+ 1 - 1
examples/example_bridge.py

@@ -61,7 +61,7 @@ if __name__ == "__main__":
 
     except Exception as e:
         print(str(e))
-        
+
     finally:
         port.flush()
         port.close()

BIN
nbus_api/__pycache__/nbus_bridge.cpython-313.pyc


+ 19 - 1
nbus_api/nbus_bridge.py

@@ -39,6 +39,7 @@ class NBusBridge:
         self.__in_acquisition = False           # flag when in acquisition
         self.__acquire_delay = acquire_delay    # intermediate delay between data fetching when data not ready
         self.__df = pd.DataFrame()              # internal data frame
+        self.__ts0 = None                       # 0-th timestamp
 
     """
     ================================================================================================================
@@ -131,7 +132,7 @@ class NBusBridge:
             # extend internal dataframe
             if parsed_packets:
                 data_frame = pd.DataFrame(parsed_packets)
-                self.__df = pd.concat([self.__df, data_frame], ignore_index=True)
+                self.__df = pd.concat([self.__df, data_frame], ignore_index=True).copy(deep=True)
             else:
                 data_frame = pd.DataFrame()
 
@@ -142,6 +143,8 @@ class NBusBridge:
             else:
                 self.__data_raw = bytearray()
 
+        self._transform_timestamp(data_frame)
+
         return data_frame
 
     def fetch_full_stream(self) -> pd.DataFrame:
@@ -156,7 +159,11 @@ class NBusBridge:
             return pd.DataFrame()
 
         df = self.__df
+        self._transform_timestamp(df)
+
         self.__df = pd.DataFrame()
+        self.__ts0 = None
+
         return df
 
     """
@@ -322,6 +329,17 @@ class NBusBridge:
 
             slave_meta.packet_size = packet_size
 
+    def _transform_timestamp(self, data_frame: pd.DataFrame) -> None:
+        """
+        Transform timestamp values in dataframe.
+        :param data_frame: dataframe to transform
+        """
+        if not data_frame.empty and "TS" in self.__df.columns:
+            if self.__ts0 is None:
+                self.__ts0 = self.__df["TS"].iloc[0]
+
+            data_frame["TS"] -= self.__ts0
+
     def _set_slave_format_from_response(self, slave_addr: NBusModuleAddress, resp_length: int, response: list[int]) \
             -> dict[tuple[NBusModuleAddress, NBusSensorAddress], NBusDataFormat]:
         """