flexitac 0.2.2__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {flexitac-0.2.2/flexitac.egg-info → flexitac-0.3.1}/PKG-INFO +44 -2
  2. {flexitac-0.2.2 → flexitac-0.3.1}/README.md +43 -1
  3. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac/__init__.py +1 -1
  4. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac/flash.py +12 -11
  5. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac/sensor.py +42 -21
  6. {flexitac-0.2.2 → flexitac-0.3.1/flexitac.egg-info}/PKG-INFO +44 -2
  7. {flexitac-0.2.2 → flexitac-0.3.1}/LICENSE +0 -0
  8. {flexitac-0.2.2 → flexitac-0.3.1}/examples/__init__.py +0 -0
  9. {flexitac-0.2.2 → flexitac-0.3.1}/examples/stream_frames.py +0 -0
  10. {flexitac-0.2.2 → flexitac-0.3.1}/examples/visualize_heatmap.py +0 -0
  11. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac/find_port.py +0 -0
  12. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac/firmware/__init__.py +0 -0
  13. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac/firmware/template.ino +0 -0
  14. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac/logging_utils.py +0 -0
  15. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac/py.typed +0 -0
  16. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac.egg-info/SOURCES.txt +0 -0
  17. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac.egg-info/dependency_links.txt +0 -0
  18. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac.egg-info/entry_points.txt +0 -0
  19. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac.egg-info/requires.txt +0 -0
  20. {flexitac-0.2.2 → flexitac-0.3.1}/flexitac.egg-info/top_level.txt +0 -0
  21. {flexitac-0.2.2 → flexitac-0.3.1}/pyproject.toml +0 -0
  22. {flexitac-0.2.2 → flexitac-0.3.1}/setup.cfg +0 -0
  23. {flexitac-0.2.2 → flexitac-0.3.1}/tests/test_find_port.py +0 -0
  24. {flexitac-0.2.2 → flexitac-0.3.1}/tests/test_flash.py +0 -0
  25. {flexitac-0.2.2 → flexitac-0.3.1}/tests/test_sensor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flexitac
3
- Version: 0.2.2
3
+ Version: 0.3.1
4
4
  Summary: Python runtime and flashing tools for FlexiTac tactile sensors
5
5
  Author-email: Wesley Maa <wesley.maa@gmail.com>
6
6
  License-Expression: MIT
@@ -22,7 +22,7 @@ Requires-Dist: flexitac[dev]; extra == "all"
22
22
  Requires-Dist: flexitac[examples]; extra == "all"
23
23
  Dynamic: license-file
24
24
 
25
- # flexitac
25
+ # PyFlexiTac
26
26
 
27
27
  Python interface for [FlexiTac](https://flexitac.github.io/) tactile sensors.
28
28
  Allows you to easily [flash](#flexitac-flash) Arduino firmware and read framed sensor data over serial.
@@ -30,6 +30,10 @@ Allows you to easily [flash](#flexitac-flash) Arduino firmware and read framed s
30
30
  Defaults target the standard FlexiTac 12x32 sensor (12 rows wired to mux
31
31
  channels 4-15). Override `--rows`, `--cols`, and `--mux-offset` for variants.
32
32
 
33
+ Serial port permissions apply to both flashing and reading. If you hit
34
+ `Permission denied` with either `flexitac-flash` or read tools like
35
+ `flexitac-stream`/`flexitac-heatmap`, see the note in [`flexitac-flash`](#flexitac-flash).
36
+
33
37
  ## Install
34
38
 
35
39
  ```bash
@@ -142,6 +146,24 @@ flexitac-flash --rows 16 --cols 32 --mux-offset 0
142
146
  flexitac-flash --port /dev/ttyUSB0 --fqbn arduino:avr:uno
143
147
  ```
144
148
 
149
+ If flashing fails with a serial `Permission denied` error:
150
+
151
+ - On Linux, add your user to the serial group, then re-login:
152
+
153
+ ```bash
154
+ sudo usermod -aG dialout "$USER"
155
+ ```
156
+
157
+ - On macOS, permissions are usually managed automatically, but if needed you can
158
+ grant temporary read/write access to the device (replace with your actual port):
159
+
160
+ ```bash
161
+ sudo chmod a+rw /dev/tty.usbmodem*
162
+ ```
163
+
164
+ As a last-resort fallback, you can run `sudo chmod 666 /dev/tty...`,
165
+ but this is overly permissive.
166
+
145
167
  Defaults: `rows=12`, `cols=32`, `baud=2000000`, `mux-offset=4` (standard
146
168
  FlexiTac 12x32 sensor wired to mux channels 4-15). The firmware is generated
147
169
  from `flexitac/firmware/template.ino` by substituting `ROW_COUNT`,
@@ -162,3 +184,23 @@ make format # ruff format + autofix
162
184
  make static-checks # ruff + mypy
163
185
  make test # pytest
164
186
  ```
187
+
188
+ ## Citation
189
+
190
+ If you use this software in research or publications, please cite the repository.
191
+ On GitHub, use **Cite this repository**.
192
+
193
+ ```bibtex
194
+ @misc{maa_pyflexitac_2026,
195
+ author = {Maa, Wesley},
196
+ title = {{PyFlexiTac}: Python runtime and flashing tools for FlexiTac tactile sensors},
197
+ year = {2026},
198
+ howpublished = {\url{https://github.com/WT-MM/PyFlexiTac}},
199
+ note = {Version 0.3.1},
200
+ }
201
+ ```
202
+
203
+ ## Acknowledgments
204
+
205
+ - [Binghao Huang](https://binghao-huang.github.io/), for the Arduino firmware template this project's firmware is derived from.
206
+ - [Naian Tao](https://tna001-ai.github.io/), for leading the [LeFlexiTac](https://tna001-ai.github.io/tactile-lerobot-website/) project that motivated this code.
@@ -1,4 +1,4 @@
1
- # flexitac
1
+ # PyFlexiTac
2
2
 
3
3
  Python interface for [FlexiTac](https://flexitac.github.io/) tactile sensors.
4
4
  Allows you to easily [flash](#flexitac-flash) Arduino firmware and read framed sensor data over serial.
@@ -6,6 +6,10 @@ Allows you to easily [flash](#flexitac-flash) Arduino firmware and read framed s
6
6
  Defaults target the standard FlexiTac 12x32 sensor (12 rows wired to mux
7
7
  channels 4-15). Override `--rows`, `--cols`, and `--mux-offset` for variants.
8
8
 
9
+ Serial port permissions apply to both flashing and reading. If you hit
10
+ `Permission denied` with either `flexitac-flash` or read tools like
11
+ `flexitac-stream`/`flexitac-heatmap`, see the note in [`flexitac-flash`](#flexitac-flash).
12
+
9
13
  ## Install
10
14
 
11
15
  ```bash
@@ -118,6 +122,24 @@ flexitac-flash --rows 16 --cols 32 --mux-offset 0
118
122
  flexitac-flash --port /dev/ttyUSB0 --fqbn arduino:avr:uno
119
123
  ```
120
124
 
125
+ If flashing fails with a serial `Permission denied` error:
126
+
127
+ - On Linux, add your user to the serial group, then re-login:
128
+
129
+ ```bash
130
+ sudo usermod -aG dialout "$USER"
131
+ ```
132
+
133
+ - On macOS, permissions are usually managed automatically, but if needed you can
134
+ grant temporary read/write access to the device (replace with your actual port):
135
+
136
+ ```bash
137
+ sudo chmod a+rw /dev/tty.usbmodem*
138
+ ```
139
+
140
+ As a last-resort fallback, you can run `sudo chmod 666 /dev/tty...`,
141
+ but this is overly permissive.
142
+
121
143
  Defaults: `rows=12`, `cols=32`, `baud=2000000`, `mux-offset=4` (standard
122
144
  FlexiTac 12x32 sensor wired to mux channels 4-15). The firmware is generated
123
145
  from `flexitac/firmware/template.ino` by substituting `ROW_COUNT`,
@@ -138,3 +160,23 @@ make format # ruff format + autofix
138
160
  make static-checks # ruff + mypy
139
161
  make test # pytest
140
162
  ```
163
+
164
+ ## Citation
165
+
166
+ If you use this software in research or publications, please cite the repository.
167
+ On GitHub, use **Cite this repository**.
168
+
169
+ ```bibtex
170
+ @misc{maa_pyflexitac_2026,
171
+ author = {Maa, Wesley},
172
+ title = {{PyFlexiTac}: Python runtime and flashing tools for FlexiTac tactile sensors},
173
+ year = {2026},
174
+ howpublished = {\url{https://github.com/WT-MM/PyFlexiTac}},
175
+ note = {Version 0.3.1},
176
+ }
177
+ ```
178
+
179
+ ## Acknowledgments
180
+
181
+ - [Binghao Huang](https://binghao-huang.github.io/), for the Arduino firmware template this project's firmware is derived from.
182
+ - [Naian Tao](https://tna001-ai.github.io/), for leading the [LeFlexiTac](https://tna001-ai.github.io/tactile-lerobot-website/) project that motivated this code.
@@ -3,4 +3,4 @@
3
3
  from flexitac.sensor import FlexiTacFrame, FlexiTacSensor
4
4
 
5
5
  __all__ = ["FlexiTacFrame", "FlexiTacSensor"]
6
- __version__ = "0.2.2"
6
+ __version__ = "0.3.1"
@@ -61,6 +61,15 @@ def detect_board() -> tuple[str, str]:
61
61
  raise FlashError(f"multiple boards detected; pass --port and --fqbn:\n{listing}")
62
62
 
63
63
 
64
+ def _require_arduino_cli() -> None:
65
+ if shutil.which("arduino-cli") is None:
66
+ raise FlashError(
67
+ "arduino-cli not found on PATH. Install it (e.g. `brew install arduino-cli`) and run "
68
+ "`arduino-cli core install arduino:avr`. See "
69
+ "https://arduino.github.io/arduino-cli/latest/installation/"
70
+ )
71
+
72
+
64
73
  def flash(
65
74
  *,
66
75
  port: str,
@@ -72,10 +81,7 @@ def flash(
72
81
  verbose: bool = False,
73
82
  ) -> None:
74
83
  """Render firmware and upload it to ``port``."""
75
- if shutil.which("arduino-cli") is None:
76
- raise FlashError(
77
- "arduino-cli not found on PATH. Install from https://arduino.github.io/arduino-cli/latest/installation/"
78
- )
84
+ _require_arduino_cli()
79
85
 
80
86
  rendered = render_template(rows=rows, cols=cols, baud=baud, mux_offset=mux_offset)
81
87
  with tempfile.TemporaryDirectory(prefix="flexitac-") as tmp:
@@ -93,7 +99,7 @@ def _run(cmd: list[str], *, verbose: bool = False, capture: bool = False) -> sub
93
99
  except FileNotFoundError as exc:
94
100
  raise FlashError(f"command not found: {cmd[0]}") from exc
95
101
  if result.returncode != 0:
96
- msg = (result.stderr or result.stdout or "").strip()
102
+ msg = (result.stderr or result.stdout or "").strip() or "(see output above)"
97
103
  raise FlashError(f"{' '.join(cmd)} failed: {msg}")
98
104
  return result
99
105
 
@@ -118,12 +124,7 @@ def main(argv: list[str] | None = None) -> int:
118
124
  logger = configure_logging(verbose=args.verbose)
119
125
 
120
126
  try:
121
- if shutil.which("arduino-cli") is None:
122
- raise FlashError(
123
- "arduino-cli not found on PATH. Install it (e.g. `brew install arduino-cli`) and run "
124
- "`arduino-cli core install arduino:avr`. See "
125
- "https://arduino.github.io/arduino-cli/latest/installation/"
126
- )
127
+ _require_arduino_cli()
127
128
 
128
129
  port, fqbn = args.port, args.fqbn
129
130
  if port is None or fqbn is None:
@@ -40,7 +40,27 @@ class FlexiTacSensor:
40
40
  noise_scale: float = 30.0,
41
41
  init_frames: int = 30,
42
42
  read_timeout_s: float = 5.0,
43
+ baseline: NDArray[np.float32] | float | None = None,
43
44
  ) -> None:
45
+ """Initialize a FlexiTac sensor.
46
+
47
+ Args:
48
+ port: Serial device path (e.g. ``/dev/ttyUSB0``).
49
+ rows: Number of tactile rows (default 12).
50
+ cols: Number of tactile columns (default 32).
51
+ baud: Serial baud rate matching the flashed firmware (default 2_000_000).
52
+ threshold: Contact-detection threshold in ADC counts above baseline.
53
+ noise_scale: Divisor used to normalize readings below ``threshold``.
54
+ init_frames: Number of frames sampled during ``calibrate()`` (default 30).
55
+ Higher values give a more robust baseline at the cost of startup time.
56
+ read_timeout_s: Maximum time ``read()`` will wait for a full frame.
57
+ baseline: If provided, skips auto-calibration on first ``read()``. Pass a
58
+ ``(rows, cols)`` array of per-pixel baselines, or a scalar applied to
59
+ every pixel. Typical resting ADC values land around 10-40 for an
60
+ unloaded sensor, so a scalar like ``20.0`` is a reasonable default
61
+ when you can't afford the startup delay of a full calibration. For
62
+ accurate contact detection, prefer letting ``calibrate()`` run.
63
+ """
44
64
  self.port = port
45
65
  self.rows = rows
46
66
  self.cols = cols
@@ -56,6 +76,9 @@ class FlexiTacSensor:
56
76
  self._baseline: NDArray[np.float32] | None = None
57
77
  self._seq = 0
58
78
 
79
+ if baseline is not None:
80
+ self._baseline = np.broadcast_to(np.float32(baseline), (rows, cols)).copy()
81
+
59
82
  def open(self) -> FlexiTacSensor:
60
83
  """Open the serial port if not already open."""
61
84
  if self._serial is None or not self._serial.is_open:
@@ -126,16 +149,24 @@ class FlexiTacSensor:
126
149
 
127
150
  def _latest_buffered_frame(self) -> NDArray[np.uint8] | None:
128
151
  latest: NDArray[np.uint8] | None = None
129
- while True:
130
- idx = self._buf.find(MARKER)
131
- if idx < 0 or len(self._buf) - idx - 2 < self._frame_bytes:
132
- break
133
- del self._buf[: idx + 2]
134
- payload = bytes(self._buf[: self._frame_bytes])
135
- del self._buf[: self._frame_bytes]
136
- latest = np.frombuffer(payload, dtype=np.uint8).reshape(self.rows, self.cols).copy()
152
+ while (frame := self._pop_frame()) is not None:
153
+ latest = frame
137
154
  return latest
138
155
 
156
+ def _pop_frame(self) -> NDArray[np.uint8] | None:
157
+ """Extract one frame from ``self._buf``; trim leading garbage if no marker."""
158
+ idx = self._buf.find(MARKER)
159
+ if idx < 0:
160
+ if len(self._buf) > 1:
161
+ del self._buf[:-1]
162
+ return None
163
+ if len(self._buf) - idx - 2 < self._frame_bytes:
164
+ return None
165
+ del self._buf[: idx + 2]
166
+ payload = bytes(self._buf[: self._frame_bytes])
167
+ del self._buf[: self._frame_bytes]
168
+ return np.frombuffer(payload, dtype=np.uint8).reshape(self.rows, self.cols).copy()
169
+
139
170
  def _normalize(self, raw: NDArray[np.uint8]) -> NDArray[np.float32]:
140
171
  assert self._baseline is not None
141
172
  signal = raw.astype(np.float32) - self._baseline - self.threshold
@@ -158,18 +189,8 @@ class FlexiTacSensor:
158
189
  if len(self._buf) > 50_000:
159
190
  del self._buf[: len(self._buf) - 50_000]
160
191
 
161
- idx = self._buf.find(MARKER)
162
- if idx < 0:
163
- if len(self._buf) > 1:
164
- del self._buf[:-1]
165
- continue
166
-
167
- if len(self._buf) - idx - 2 < self._frame_bytes:
168
- continue
169
-
170
- del self._buf[: idx + 2]
171
- payload = bytes(self._buf[: self._frame_bytes])
172
- del self._buf[: self._frame_bytes]
173
- return np.frombuffer(payload, dtype=np.uint8).reshape(self.rows, self.cols).copy()
192
+ frame = self._pop_frame()
193
+ if frame is not None:
194
+ return frame
174
195
 
175
196
  raise TimeoutError(f"timed out after {self.read_timeout_s:.2f}s waiting for a frame")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flexitac
3
- Version: 0.2.2
3
+ Version: 0.3.1
4
4
  Summary: Python runtime and flashing tools for FlexiTac tactile sensors
5
5
  Author-email: Wesley Maa <wesley.maa@gmail.com>
6
6
  License-Expression: MIT
@@ -22,7 +22,7 @@ Requires-Dist: flexitac[dev]; extra == "all"
22
22
  Requires-Dist: flexitac[examples]; extra == "all"
23
23
  Dynamic: license-file
24
24
 
25
- # flexitac
25
+ # PyFlexiTac
26
26
 
27
27
  Python interface for [FlexiTac](https://flexitac.github.io/) tactile sensors.
28
28
  Allows you to easily [flash](#flexitac-flash) Arduino firmware and read framed sensor data over serial.
@@ -30,6 +30,10 @@ Allows you to easily [flash](#flexitac-flash) Arduino firmware and read framed s
30
30
  Defaults target the standard FlexiTac 12x32 sensor (12 rows wired to mux
31
31
  channels 4-15). Override `--rows`, `--cols`, and `--mux-offset` for variants.
32
32
 
33
+ Serial port permissions apply to both flashing and reading. If you hit
34
+ `Permission denied` with either `flexitac-flash` or read tools like
35
+ `flexitac-stream`/`flexitac-heatmap`, see the note in [`flexitac-flash`](#flexitac-flash).
36
+
33
37
  ## Install
34
38
 
35
39
  ```bash
@@ -142,6 +146,24 @@ flexitac-flash --rows 16 --cols 32 --mux-offset 0
142
146
  flexitac-flash --port /dev/ttyUSB0 --fqbn arduino:avr:uno
143
147
  ```
144
148
 
149
+ If flashing fails with a serial `Permission denied` error:
150
+
151
+ - On Linux, add your user to the serial group, then re-login:
152
+
153
+ ```bash
154
+ sudo usermod -aG dialout "$USER"
155
+ ```
156
+
157
+ - On macOS, permissions are usually managed automatically, but if needed you can
158
+ grant temporary read/write access to the device (replace with your actual port):
159
+
160
+ ```bash
161
+ sudo chmod a+rw /dev/tty.usbmodem*
162
+ ```
163
+
164
+ As a last-resort fallback, you can run `sudo chmod 666 /dev/tty...`,
165
+ but this is overly permissive.
166
+
145
167
  Defaults: `rows=12`, `cols=32`, `baud=2000000`, `mux-offset=4` (standard
146
168
  FlexiTac 12x32 sensor wired to mux channels 4-15). The firmware is generated
147
169
  from `flexitac/firmware/template.ino` by substituting `ROW_COUNT`,
@@ -162,3 +184,23 @@ make format # ruff format + autofix
162
184
  make static-checks # ruff + mypy
163
185
  make test # pytest
164
186
  ```
187
+
188
+ ## Citation
189
+
190
+ If you use this software in research or publications, please cite the repository.
191
+ On GitHub, use **Cite this repository**.
192
+
193
+ ```bibtex
194
+ @misc{maa_pyflexitac_2026,
195
+ author = {Maa, Wesley},
196
+ title = {{PyFlexiTac}: Python runtime and flashing tools for FlexiTac tactile sensors},
197
+ year = {2026},
198
+ howpublished = {\url{https://github.com/WT-MM/PyFlexiTac}},
199
+ note = {Version 0.3.1},
200
+ }
201
+ ```
202
+
203
+ ## Acknowledgments
204
+
205
+ - [Binghao Huang](https://binghao-huang.github.io/), for the Arduino firmware template this project's firmware is derived from.
206
+ - [Naian Tao](https://tna001-ai.github.io/), for leading the [LeFlexiTac](https://tna001-ai.github.io/tactile-lerobot-website/) project that motivated this code.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes