feat(status): add Brother 32-byte status block parser#29
Conversation
Implements the StatusBlockParser per ADR 0006 — decodes the binary status block that Brother PT-Series and QL-Series printers return from ESC i S and emit during print jobs. Key design points: - One parser handles both PT and QL families (the layout is shared with family-specific quirks in media-type codes, reserved bytes, and tape/text-colour bytes 24-25 which only PT populates) - Frozen dataclass output (StatusBlock) — consumers never mutate - Unknown enum values map to UNKNOWN rather than raising, so unfamiliar hardware doesn't crash the parser - PrinterError IntFlag covers all error bits documented in both Brother spec versions (PT v1.02 and QL v1.01) - Phase number decoded as big-endian (Brother convention: bytes 20+21) - Convenience properties: is_ready, is_printing Backend project structure: - pyproject.toml with FastAPI/Pydantic/SQLModel/pytest/ruff/mypy stack - pytest.ini config with strict markers, coverage 80% floor - conftest.py with --hardware skip-by-default flag - 24 unit tests cover: size validation, PT decoding, QL decoding, error flag combinations, phase transitions, unknown value fallback, raw bytes preservation, frozen dataclass References: - Brother Raster Command Reference v1.02 (PT-E550W/P750W/P710BT) - Brother Raster Command Reference v1.01 (QL-800/810W/820NWB) - ADR 0004 — Plugin architecture (consumers of this parser) - ADR 0006 — Status sources by phase Refs #11, #19
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces the core status monitoring functionality for the label printer hub, enabling the system to decode binary status blocks from Brother printers. It also sets up the foundational backend architecture, ensuring a strict, type-safe development environment for future printer-model plugins. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
There was a problem hiding this comment.
Code Review
This pull request introduces a status block parser for Brother PT and QL series label printers, including comprehensive unit tests and project configuration. The implementation decodes 32-byte binary status messages into structured dataclasses. Feedback focuses on simplifying the StatusBlock dataclass by using IntFlag more idiomatically, refactoring the error decoding logic to use bitwise operations, and removing a forbidden dependency on requests type stubs. Additionally, a test assertion was identified as being too loose regarding enum values.
| error_flags_1: int | ||
| error_flags_2: int | ||
| media_width_mm: int | ||
| media_type: MediaType | ||
| media_length_mm: int | ||
| mode: int | ||
| status_type: StatusType | ||
| phase_type: PhaseType | ||
| phase_number: int | ||
| notification: NotificationCode | ||
| tape_color: TapeColor | ||
| text_color: TextColor | ||
|
|
||
| errors: list[PrinterError] = field(default_factory=list) |
There was a problem hiding this comment.
The StatusBlock dataclass can be simplified. The error_flags_1 and error_flags_2 fields are redundant as their information is already captured in the errors field (and the raw bytes are preserved in raw). Furthermore, since PrinterError is an IntFlag, it is more idiomatic and efficient to store it as a single flag value rather than a list of flags. This also ensures true immutability, as a list remains mutable even within a frozen dataclass.
| error_flags_1: int | |
| error_flags_2: int | |
| media_width_mm: int | |
| media_type: MediaType | |
| media_length_mm: int | |
| mode: int | |
| status_type: StatusType | |
| phase_type: PhaseType | |
| phase_number: int | |
| notification: NotificationCode | |
| tape_color: TapeColor | |
| text_color: TextColor | |
| errors: list[PrinterError] = field(default_factory=list) | |
| media_width_mm: int | |
| media_type: MediaType | |
| media_length_mm: int | |
| mode: int | |
| status_type: StatusType | |
| phase_type: PhaseType | |
| phase_number: int | |
| notification: NotificationCode | |
| tape_color: TapeColor | |
| text_color: TextColor | |
| errors: PrinterError = PrinterError.NONE |
| def _decode_errors(error_flags_1: int, error_flags_2: int) -> list[PrinterError]: | ||
| """Translate Brother error info 1+2 into a list of distinct flags.""" | ||
| flags: list[PrinterError] = [] | ||
| bit_map_1: list[tuple[int, PrinterError]] = [ | ||
| (0x01, PrinterError.NO_MEDIA), | ||
| (0x02, PrinterError.END_OF_MEDIA), | ||
| (0x04, PrinterError.CUTTER_JAM), | ||
| (0x08, PrinterError.WEAK_BATTERIES), | ||
| (0x10, PrinterError.PRINTER_IN_USE), | ||
| (0x20, PrinterError.PRINTER_TURNED_OFF), | ||
| (0x40, PrinterError.HIGH_VOLTAGE_ADAPTER), | ||
| (0x80, PrinterError.FAN_MOTOR_ERROR), | ||
| ] | ||
| bit_map_2: list[tuple[int, PrinterError]] = [ | ||
| (0x01, PrinterError.REPLACE_MEDIA), | ||
| (0x02, PrinterError.EXPANSION_BUFFER_FULL), | ||
| (0x04, PrinterError.COMMUNICATION_ERROR), | ||
| (0x10, PrinterError.COVER_OPEN), | ||
| (0x20, PrinterError.OVERHEATING), | ||
| (0x40, PrinterError.MEDIA_CANNOT_BE_FED), | ||
| (0x80, PrinterError.SYSTEM_ERROR), | ||
| ] | ||
| for mask, flag in bit_map_1: | ||
| if error_flags_1 & mask: | ||
| flags.append(flag) | ||
| for mask, flag in bit_map_2: | ||
| if error_flags_2 & mask: | ||
| flags.append(flag) | ||
| return flags |
There was a problem hiding this comment.
The _decode_errors function is unnecessarily complex. Since the PrinterError IntFlag members are already defined with bit shifts that match the Brother status block layout (where byte 8 is the low byte and byte 9 is the high byte of a 16-bit error field), you can replace the manual bit mapping with a simple bitwise operation or int.from_bytes.
| def _decode_errors(error_flags_1: int, error_flags_2: int) -> list[PrinterError]: | |
| """Translate Brother error info 1+2 into a list of distinct flags.""" | |
| flags: list[PrinterError] = [] | |
| bit_map_1: list[tuple[int, PrinterError]] = [ | |
| (0x01, PrinterError.NO_MEDIA), | |
| (0x02, PrinterError.END_OF_MEDIA), | |
| (0x04, PrinterError.CUTTER_JAM), | |
| (0x08, PrinterError.WEAK_BATTERIES), | |
| (0x10, PrinterError.PRINTER_IN_USE), | |
| (0x20, PrinterError.PRINTER_TURNED_OFF), | |
| (0x40, PrinterError.HIGH_VOLTAGE_ADAPTER), | |
| (0x80, PrinterError.FAN_MOTOR_ERROR), | |
| ] | |
| bit_map_2: list[tuple[int, PrinterError]] = [ | |
| (0x01, PrinterError.REPLACE_MEDIA), | |
| (0x02, PrinterError.EXPANSION_BUFFER_FULL), | |
| (0x04, PrinterError.COMMUNICATION_ERROR), | |
| (0x10, PrinterError.COVER_OPEN), | |
| (0x20, PrinterError.OVERHEATING), | |
| (0x40, PrinterError.MEDIA_CANNOT_BE_FED), | |
| (0x80, PrinterError.SYSTEM_ERROR), | |
| ] | |
| for mask, flag in bit_map_1: | |
| if error_flags_1 & mask: | |
| flags.append(flag) | |
| for mask, flag in bit_map_2: | |
| if error_flags_2 & mask: | |
| flags.append(flag) | |
| return flags | |
| def _decode_errors(error_flags_1: int, error_flags_2: int) -> PrinterError: | |
| """Translate Brother error info 1+2 into a single IntFlag.""" | |
| return PrinterError((error_flags_2 << 8) | error_flags_1) |
| "respx>=0.21", | ||
| "ruff>=0.8", | ||
| "mypy>=1.13", | ||
| "types-requests", |
There was a problem hiding this comment.
The types-requests dependency should be removed. The repository style guide explicitly forbids the use of the requests library in favor of httpx (see 'Things to flag', line 30). Including type stubs for a forbidden library is inconsistent and adds unnecessary bloat to the dev dependencies.
References
- The style guide forbids the use of the
requestslibrary, recommendinghttpxinstead. (link)
| def test_qlseries_has_no_tape_colour(self) -> None: | ||
| sb = StatusBlockParser.parse(QL_READY_12MM_CONTINUOUS) | ||
| # QL doesn't populate these — bytes should land on zero/unknown | ||
| assert sb.tape_color in (TapeColor.UNKNOWN, TapeColor.WHITE) or sb.tape_color is None |
There was a problem hiding this comment.
The assertion in test_qlseries_has_no_tape_colour is too loose and contains an impossible condition. sb.tape_color is an IntEnum and will never be None. Since the QL-Series does not populate this byte, it should consistently map to TapeColor.UNKNOWN (0x00).
| assert sb.tape_color in (TapeColor.UNKNOWN, TapeColor.WHITE) or sb.tape_color is None | |
| assert sb.tape_color == TapeColor.UNKNOWN |
There was a problem hiding this comment.
Pull request overview
Introduces the backend’s initial Python project skeleton and adds a Brother PT/QL 32-byte status block parser (ESC i S) with unit tests, aligning with ADR 0006’s “status sources by phase”.
Changes:
- Added
StatusBlockParser+StatusBlockdataclass and related enums/flags for decoding Brother 32-byte status blocks. - Added pytest configuration (including
--hardwareopt-in skipping) and a unit test suite covering parsing behaviors and edge cases. - Added backend packaging/tooling configuration in
pyproject.toml(FastAPI/SQLModel stack, ruff, mypy strict, coverage).
Reviewed changes
Copilot reviewed 4 out of 14 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
backend/app/services/status_block.py |
New status-block parser, enums, error decoding, and StatusBlock dataclass. |
backend/tests/unit/services/test_status_block.py |
Unit tests for parsing PT/QL fixtures, errors, phases, and unknown-enum fallbacks. |
backend/tests/conftest.py |
Pytest --hardware flag and default skip behavior for hardware tests. |
backend/pyproject.toml |
Backend project dependencies and tooling configuration (ruff/mypy/pytest/coverage). |
.gitignore |
Ignores backend virtualenv directory. |
| errors: list[PrinterError] = field(default_factory=list) | ||
|
|
| # QL doesn't populate these — bytes should land on zero/unknown | ||
| assert sb.tape_color in (TapeColor.UNKNOWN, TapeColor.WHITE) or sb.tape_color is None |
| def test_dataclass_is_frozen(self) -> None: | ||
| sb = StatusBlockParser.parse(PT_READY_12MM_WHITE_BLACK) | ||
| with pytest.raises((AttributeError, Exception)): # FrozenInstanceError or similar | ||
| sb.media_width_mm = 99 # type: ignore[misc] |
| def _decode_errors(error_flags_1: int, error_flags_2: int) -> list[PrinterError]: | ||
| """Translate Brother error info 1+2 into a list of distinct flags.""" | ||
| flags: list[PrinterError] = [] | ||
| bit_map_1: list[tuple[int, PrinterError]] = [ | ||
| (0x01, PrinterError.NO_MEDIA), | ||
| (0x02, PrinterError.END_OF_MEDIA), | ||
| (0x04, PrinterError.CUTTER_JAM), | ||
| (0x08, PrinterError.WEAK_BATTERIES), | ||
| (0x10, PrinterError.PRINTER_IN_USE), | ||
| (0x20, PrinterError.PRINTER_TURNED_OFF), | ||
| (0x40, PrinterError.HIGH_VOLTAGE_ADAPTER), | ||
| (0x80, PrinterError.FAN_MOTOR_ERROR), | ||
| ] | ||
| bit_map_2: list[tuple[int, PrinterError]] = [ | ||
| (0x01, PrinterError.REPLACE_MEDIA), | ||
| (0x02, PrinterError.EXPANSION_BUFFER_FULL), | ||
| (0x04, PrinterError.COMMUNICATION_ERROR), | ||
| (0x10, PrinterError.COVER_OPEN), | ||
| (0x20, PrinterError.OVERHEATING), | ||
| (0x40, PrinterError.MEDIA_CANNOT_BE_FED), | ||
| (0x80, PrinterError.SYSTEM_ERROR), | ||
| ] | ||
| for mask, flag in bit_map_1: | ||
| if error_flags_1 & mask: | ||
| flags.append(flag) | ||
| for mask, flag in bit_map_2: |
Both reviewers caught the same set of issues — addressing them in one follow-up commit on the feature branch: Gemini findings (all addressed): - StatusBlock.errors changed from list[PrinterError] (mutable, breaks frozen=True guarantee) to a single PrinterError IntFlag value. Use bitwise membership: 'PrinterError.NO_MEDIA in sb.errors' still works because Python IntFlag supports it natively. - error_flags_1 / error_flags_2 fields removed — redundant when raw bytes are preserved in .raw[8] / .raw[9]. - _decode_errors() collapsed to a one-liner using IntFlag's CONFORM boundary: combined = info1 | (info2 << 8); PrinterError(combined). No more bit_map allocations on each parse. - types-requests removed from dev dependencies — the project uses httpx exclusively (per CLAUDE.md and .gemini/styleguide.md). Copilot findings (all addressed): - frozen=True now actually frozen (no list field). FrozenInstanceError is raised on attribute mutation. - test_qlseries_has_no_tape_colour assertion tightened to '== TapeColor.UNKNOWN' (was permissive 'in (UNKNOWN, WHITE) or None'). - test_dataclass_is_frozen catches the specific dataclasses.FrozenInstanceError instead of bare Exception. - bit_map_1 / bit_map_2 module-level constants no longer needed — the IntFlag bitwise operation replaces them entirely. Test count: 24 → 25 (added test_errors_field_is_immutable_intflag to guard against regression to mutable list).
|
Thanks both — every finding addressed in . Gemini:
Copilot:
Added one new test ( @gemini-code-assist please re-review. |
There was a problem hiding this comment.
Code Review
This pull request introduces a status block parser for Brother PT/QL series printers, including comprehensive enums and unit tests, and sets up the backend project structure. Feedback focuses on ensuring PrinterError flags correctly mask unknown bits by explicitly setting the CONFORM boundary for IntFlag in Python 3.11+. Additionally, a request was made to justify new dependencies in the project configuration as per the repository's style guide.
| INCOMPATIBLE = 0xFF | ||
|
|
||
|
|
||
| class PrinterError(IntFlag): |
There was a problem hiding this comment.
In Python 3.11+, the default boundary for IntFlag is KEEP, which preserves unknown bits. This contradicts the docstring for _decode_errors (line 231) and could cause is_ready to return False if the printer returns reserved bits in the error bytes. Explicitly setting boundary=FlagBoundary.CONFORM will mask off unknown bits during instantiation, matching the intended behavior.
| class PrinterError(IntFlag): | |
| class PrinterError(IntFlag, boundary=FlagBoundary.CONFORM): |
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
| from enum import IntEnum, IntFlag |
| to the low byte (bits 0-7) and error info 2 maps to the high byte | ||
| (bits 8-15). Combining them is therefore a single bitwise operation. | ||
|
|
||
| Unknown bits are silently dropped — Python ``IntFlag`` (default |
There was a problem hiding this comment.
Update the docstring to reflect that CONFORM is being explicitly set on the PrinterError class, as it is not the default behavior in Python 3.11+.
| Unknown bits are silently dropped — Python ``IntFlag`` (default | |
| Unknown bits are silently dropped — Python ``IntFlag`` (with ``CONFORM`` boundary) masks them off automatically. |
| dependencies = [ | ||
| "fastapi>=0.115", | ||
| "uvicorn[standard]>=0.32", | ||
| "pydantic>=2.9", | ||
| "pydantic-settings>=2.6", | ||
| "sqlmodel>=0.0.22", | ||
| "aiosqlite>=0.20", | ||
| "jinja2>=3.1", | ||
| "python-multipart>=0.0.12", | ||
| "httpx>=0.28", | ||
| "pillow>=11.0", | ||
| "qrcode[pil]>=8.0", | ||
| "brother-ql>=0.9.4", | ||
| "ptouch>=1.1.0", | ||
| "pysnmp>=6.2", | ||
| ] |
There was a problem hiding this comment.
The PR description mentions the FastAPI/SQLModel stack but does not provide a rationale for several new dependencies, including brother-ql, ptouch, pillow, and qrcode. Per the repository style guide (rule 31), new dependencies should be justified in the PR description.
References
- New dependencies without rationale in PR description should be flagged. (link)
## 1.0.0 (2026-05-10) * ci: make Python lint/test job branch-tolerant for dependabot PRs (#30) ([6ee8e16](6ee8e16)), closes [#30](#30) [2-#9](https://github.com/2-/issues/9) [#2](#2) [#3](#3) [#4](#4) [#6](#6) [#7](#7) [#8](#8) [#9](#9) * ci(deps): bump the actions-all group across 1 directory with 14 updates (#9) ([a88a027](a88a027)), closes [#9](#9) * chore: initial repository skeleton ([0319e7e](0319e7e)) * chore(deps-dev): bump @commitlint/cli from 19.8.1 to 21.0.0 (#3) ([2642b26](2642b26)), closes [#3](#3) * chore(deps-dev): bump @commitlint/config-conventional (#4) ([44be5f9](44be5f9)), closes [#4](#4) * chore(deps-dev): bump @semantic-release/exec from 6.0.3 to 7.1.0 (#2) ([a8097b0](a8097b0)), closes [#2](#2) * chore(deps-dev): bump @semantic-release/github from 11.0.6 to 12.0.8 (#8) ([098e766](098e766)), closes [#8](#8) * chore(deps-dev): bump conventional-changelog-conventionalcommits (#7) ([9f5370f](9f5370f)), closes [#7](#7) * chore(deps-dev): bump semantic-release from 24.2.9 to 25.0.3 (#6) ([306010f](306010f)), closes [#6](#6) * feat(status): add Brother 32-byte status block parser (#29) ([ced0ff8](ced0ff8)), closes [#29](#29) [#11](#11) [#19](#19) [#29](#29) * docs: refactor — ADRs in docs/decisions/, policies in docs/policies/, slim CLAUDE.md ([51c2cf1](51c2cf1)), closes [#1](#1) * docs(decisions): ADR 0006 — PT vs QL ESC i S behaviour from Phase-0 hardware test ([0d12c63](0d12c63)), closes [#12](#12) [#11](#11) * docs(decisions): ADR 0012 — layout management; clarify integration push/pull capabilities ([effcbdf](effcbdf)), closes [#17](#17) [#19](#19) * docs(decisions): ADR 0013 + cart UI spec + AI-review workflow ([c72dc85](c72dc85)), closes [#26](#26) [#27](#27) [#28](#28) * docs(docker): document image tag scheme (latest, 1.0.0, 1.0, 1) ([1f72396](1f72396)) * docs(examples): sample compose files for standalone/Traefik/Pangolin/Caddy ([a2f6f3d](a2f6f3d)) [skip ci]
…TRIBUTING/AI configs Captures recurring AI-reviewer findings (Gemini Code Assist + Copilot) so contributors and AI tools alike can avoid the same mistakes in the next PR. Initial entries from PR #29 (StatusBlockParser), PR #30 (CI cache fix), and PR #32 (release trigger): Python: - frozen=True dataclasses must not have mutable fields (list, dict, set) - IntFlag is the idiomatic type for combined error/flag states - IntFlag(combined_int) replaces manual bit-mapping loops - Don't include type stubs for forbidden libraries (e.g. types-requests when the project uses httpx exclusively) - Test assertions should be tight (== UNKNOWN, not 'in (A, B) or None') - Catch specific exception types (FrozenInstanceError) not bare Exception GitHub Actions: - workflow_dispatch input names: no hyphens (parsed as subtraction) - workflow_dispatch jobs publishing must have branch guards - semantic-release evaluates full history since last tag, not just 'today's' commits — be precise when describing skip behaviour Process: - Wait for AI reviewers (~1-2 min) even on small PRs; --admin merge before review forfeits the value - Side-effects must be in PR description; reviewers shouldn't have to discover them via line-by-line diff reading Cross-references added in: - CLAUDE.md (point #6 of 'Read these first') - CONTRIBUTING.md (after the §7 Code review section) - .github/copilot-instructions.md (new 'Repeated patterns' section) - .gemini/styleguide.md ('Recurring review patterns' section, asks Gemini to suggest updates to the file rather than re-flagging the same finding) The file is meant to grow: when an AI review surfaces a new recurring pattern, the rule is to add it in the same follow-up commit.
…TRIBUTING/AI configs (#33) Captures recurring AI-reviewer findings (Gemini Code Assist + Copilot) so contributors and AI tools alike can avoid the same mistakes in the next PR. Initial entries from PR #29 (StatusBlockParser), PR #30 (CI cache fix), and PR #32 (release trigger): Python: - frozen=True dataclasses must not have mutable fields (list, dict, set) - IntFlag is the idiomatic type for combined error/flag states - IntFlag(combined_int) replaces manual bit-mapping loops - Don't include type stubs for forbidden libraries (e.g. types-requests when the project uses httpx exclusively) - Test assertions should be tight (== UNKNOWN, not 'in (A, B) or None') - Catch specific exception types (FrozenInstanceError) not bare Exception GitHub Actions: - workflow_dispatch input names: no hyphens (parsed as subtraction) - workflow_dispatch jobs publishing must have branch guards - semantic-release evaluates full history since last tag, not just 'today's' commits — be precise when describing skip behaviour Process: - Wait for AI reviewers (~1-2 min) even on small PRs; --admin merge before review forfeits the value - Side-effects must be in PR description; reviewers shouldn't have to discover them via line-by-line diff reading Cross-references added in: - CLAUDE.md (point #6 of 'Read these first') - CONTRIBUTING.md (after the §7 Code review section) - .github/copilot-instructions.md (new 'Repeated patterns' section) - .gemini/styleguide.md ('Recurring review patterns' section, asks Gemini to suggest updates to the file rather than re-flagging the same finding) The file is meant to grow: when an AI review surfaces a new recurring pattern, the rule is to add it in the same follow-up commit.
## 0.1.0 (2026-05-10) * docs: refactor — ADRs in docs/decisions/, policies in docs/policies/, slim CLAUDE.md ([51c2cf1](51c2cf1)), closes [#1](#1) * docs(ci): document mypy as hard gate; clarify CI gate policy in CONTRIBUTING (#31) ([e1e6f18](e1e6f18)), closes [#31](#31) [#30](#30) [#30](#30) [#30](#30) * docs(decisions): ADR 0006 — PT vs QL ESC i S behaviour from Phase-0 hardware test ([0d12c63](0d12c63)), closes [#12](#12) [#11](#11) * docs(decisions): ADR 0012 — layout management; clarify integration push/pull capabilities ([effcbdf](effcbdf)), closes [#17](#17) [#19](#19) * docs(decisions): ADR 0013 + cart UI spec + AI-review workflow ([c72dc85](c72dc85)), closes [#26](#26) [#27](#27) [#28](#28) * docs(docker): document image tag scheme (latest, 1.0.0, 1.0, 1) ([1f72396](1f72396)) * docs(examples): sample compose files for standalone/Traefik/Pangolin/Caddy ([a2f6f3d](a2f6f3d)) * docs(learnings): add code-review-patterns + reference from CLAUDE/CONTRIBUTING/AI configs (#33) ([0fc61d2](0fc61d2)), closes [#33](#33) [#29](#29) [#30](#30) [#32](#32) [#6](#6) * ci: make Python lint/test job branch-tolerant for dependabot PRs (#30) ([6ee8e16](6ee8e16)), closes [#30](#30) [2-#9](https://github.com/2-/issues/9) [#2](#2) [#3](#3) [#4](#4) [#6](#6) [#7](#7) [#8](#8) [#9](#9) * ci(deps): bump the actions-all group across 1 directory with 14 updates (#9) ([a88a027](a88a027)), closes [#9](#9) * ci(release): trigger releases via cron + workflow_dispatch only (#32) ([9941958](9941958)), closes [#32](#32) [#32](#32) [#32](#32) * chore(deps-dev): bump @commitlint/cli from 19.8.1 to 21.0.0 (#3) ([2642b26](2642b26)), closes [#3](#3) * chore(deps-dev): bump @commitlint/config-conventional (#4) ([44be5f9](44be5f9)), closes [#4](#4) * chore(deps-dev): bump @semantic-release/exec from 6.0.3 to 7.1.0 (#2) ([a8097b0](a8097b0)), closes [#2](#2) * chore(deps-dev): bump @semantic-release/github from 11.0.6 to 12.0.8 (#8) ([098e766](098e766)), closes [#8](#8) * chore(deps-dev): bump conventional-changelog-conventionalcommits (#7) ([9f5370f](9f5370f)), closes [#7](#7) * chore(deps-dev): bump semantic-release from 24.2.9 to 25.0.3 (#6) ([306010f](306010f)), closes [#6](#6) * chore(release): 1.0.0 ([15abeb3](15abeb3)) * feat(status): add Brother 32-byte status block parser (#29) ([ced0ff8](ced0ff8)), closes [#29](#29) [#11](#11) [#19](#19) [#29](#29) [skip ci]
Summary
First real code in the project — implements the StatusBlockParser per ADR 0006. Decodes the binary status block that Brother PT-Series and QL-Series printers return on ESC i S and emit during print jobs.
This PR also lays down the backend project skeleton (pyproject.toml with FastAPI/SQLModel/pytest/ruff/mypy stack, conftest with
--hardwareskip flag, package layout per ADR 0001 §3.2).Linked issue
Refs #11, #19
Type of change
Hardware tested on
Test coverage
Checklist
feat(status): …)Design notes
Why one parser for both PT and QL families:
UNKNOWNrather than raise — keeps the parser robust against firmware quirks and future code values.StatusBlockis afrozen=True, slots=Truedataclass — consumers can't accidentally mutate it.Files
backend/pyproject.toml— FastAPI/Pydantic/SQLModel/brother_ql/ptouch/pysnmp deps; ruff strict lint; mypy strictbackend/app/services/status_block.py— parser + StatusBlock dataclass + enumsbackend/tests/unit/services/test_status_block.py— 24 testsbackend/tests/conftest.py— pytest config with--hardwareskip-by-default flag