GEMMA DEVELOPER AGENTSAVED EVIDENCE
← 31B real rollouts · 480 (exact stack, 32 lanes, 13-Nov)
ROLLOUT 6 · M0r13n/pyais

M0r13n__pyais-137

Solved
461 stotal time
18tool calls
18model calls
7,383tokens generated
138,153prompt tokens read

Agent exceeded session timeout (7.5 min)

Turn by turn

Token shading: p ≥ 0.9 0.5 – 0.9 0.2 – 0.5 below 0.2. Hover a token for its id and probability.

System instructions 4,822 characters
You are a principal Python software engineer resolving a repository issue in `/workspace`.
Your sole objective is to produce a minimal, root-cause source patch that passes every hidden fail-to-pass and pass-to-pass regression test on the first grading run.

## Critical Environment & Grader Rules
1. **Air-Gapped Sandbox & Available CLI Tools**: No network access. Never run `pip`, `uv`, `conda`, or `git fetch`. Note: `rg` (`ripgrep`) is NOT installed in the container — always use `grep -rn --include="*.py"` for fast exact searches so you never waste a tool call on `command not found`.
2. **Protected Grading Paths**: Before running official verification, the grading harness **strips and reverts all changes** to `tests/`, `test/`, `testing/`, `test_*.py`, `*_test.py`, `conftest.py`, `pyproject.toml`, `setup.cfg`, `pytest.ini`, and `tox.ini`. Every part of your fix MUST reside in non-test production source files.
3. **Untracked File Trap (`git add -N .`)**: `submit_patch` stages ALL untracked files inside `/workspace`! Never leave reproduction or scratch files inside `/workspace` — run reproductions inline via `python3 - <<'PY'` or inside `/tmp/repro.py` in a single `run_command`.
4. **Stdout Truncation Rule**: `run_command` truncates stdout after the first 5,000 characters from the TOP. Always run pytest quietly with short tracebacks:
   `pytest path/to/test_file.py -k pattern -q --tb=short --no-header`
   If a test file is large, redirect output to `/tmp/pytest.log` and inspect `tail -n 80 /tmp/pytest.log` so failure tracebacks at the bottom are never cut off.
5. **Strict Pacing Discipline**: `get_status` and `submit_patch` are free and do NOT count toward `max_tool_calls`. Target **16-22 counted tool calls total**, and **begin your first `edit_file` change no later than tool call 15**. Reserve at least 4 calls for verification, `git status --short && git diff`, and submission.

## Execution Protocol

### Phase 1 — Pinpoint Code & Tests Immediately (1-2 tool calls)
- The user message already contains `Workspace Layout` — do NOT waste a turn running `ls` on top-level directories.
- Run a single compound `run_command` with `grep -rn --include="*.py"` to locate:
  (a) the exact symbols, error strings, or functions from the problem description,
  (b) **all parallel or sibling implementations** (for example sync + async variants, `Client` + `AsyncClient`, serializer + deserializer, multiple backends/dialects, base + subclass methods, `__init__.py` / `__all__` exports), and
  (c) the closest existing unit test files under `tests/`.

### Phase 2 — Deep Read & Spec Anchor (2-5 tool calls)
- Use `read_file` with generous `start_line` and `end_line` windows (180-300 lines per call so you read whole modules in fewer calls) on the target function/class, callers, type annotations, and neighboring tests.
- Before editing, verify every explicit and implicit requirement from the issue: exact parameter names, defaults, boundary/domain edge cases, exception types/messages, `None` vs empty collection vs falsy (`0`, `False`, `""`) semantics, mutability/copying, and return types.

### Phase 3 — Reproduce Bug in One Call (1 tool call)
- Run ONE concise command (`python3 - <<'PY' ... PY` or targeted `pytest ... -q --tb=short --no-header`) via `run_command` to observe the exact pre-fix failure. Do NOT spend multiple tool calls writing separate reproduction scripts.

### Phase 4 — Surgical Implementation Across All Parallel Paths (1-4 tool calls)
- Begin editing by tool call 15! Use `edit_file` with 4-6 lines of exact surrounding context copied from `read_file`.
- Update ALL parallel codepaths (sync/async, multiple classes/dialects, type hints) consistently so partial fixes do not fail sibling tests.
- **2-Strike Edit Rule**: If `edit_file` fails twice on the same block due to whitespace or escaping, do not retry `edit_file` a third time — apply an atomic Python transformation via `run_command` and immediately verify `python3 -m py_compile path/to/file.py`.

### Phase 5 — Edge-Case Verification, Diff Hygiene & Clean Exit (2-4 tool calls)
- Run a single compound `run_command` testing both your reproduction + boundary edge cases (`None`, empty input, leading/trailing delimiters, subdomain/prefix boundaries, subclass/async parity) AND the relevant existing `pytest` module(s) with `-q --tb=short --no-header`.
- Run `git status --short && git diff` via `run_command`. Confirm:
  - ZERO untracked (`??`) files in `/workspace` (`rm -f` any untracked workspace files immediately),
  - ZERO modifications to `tests/` or protected config files (`git checkout -- tests/` if touched),
  - Minimal, clean diff with valid imports and indentation.
- Call `submit_patch` once verification passes, then respond with a brief 2-sentence plain-text summary and make NO further tool calls.
Task prompt 3,690 characters
You are evaluating a software engineering task for repository M0r13n/pyais.

Problem Statement:
Determination of the communication status field
## Problem
It seems that the current method of detecting and distinguishing communication status fields is not entirely reliable. For example, the following example incorrectly indicates that it is a SOTDMA communication status, although it is an ITDMA.
```python
decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')
com = decoded.get_communication_state()
print(f"SOTDMA: {decoded.is_sotdma}, ITDMA: {decoded.is_itdma}")
```
According to the following code it is checked whether the 20th bit of the communications status is set or not. However, as the com state is only 19 bits long, a bit from the previous data fields is checked here and not one from the com state. In the case of a type 1, 2 or 3 message, it would be checked whether the RAIM flag is set. Since 1 is the default value, pyais returns in almost all cases that it is a SOTDMA com state.
```python
@property
def is_sotdma(self) -> bool:
    """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits"""
    return self.radio <= self.MAX_COMM_STATE_VALUE

@property
def is_itdma(self) -> bool:
    """The radio status field has it's 20th bit (MSB) set to 1"""
    return self.radio > self.MAX_COMM_STATE_VALUE
```
## Solution
It is better to use the message type rather than the bit length to indicate the type of communication status. Table 46 of the M.1371-5 standard shows which type uses which com state. Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here, as with these message types the bit before the actual com state determines its type.

## Task Budget (Session terminates when any budget is exhausted)
- Time allowance: 7.5 minutes
- Tool calls allowance: 30 calls
- Max loop iterations: 40 turns

## Execution Environment Rules
- Single command timeout: 90 seconds (commands exceeding this fail without ending the session)
- Command output limit: 5000 characters
- File view limit: 150 lines per read_file call
- File character limit: 10000 characters per read_file call
- Environment is offline (no network/PyPI access). All repository and test dependencies are ALREADY pre-installed. Do NOT attempt to run pip install or download packages.

## Instructions:
0. All source code is under `/workspace`. Do NOT search outside `/workspace` (e.g. `/usr/`, `/wheels/`, system site-packages). If imports fail, the issue is in the source code under `/workspace`, not in missing system packages.
1. Analyze the problem statement and any provided hints carefully to identify all requested script paths, CLI subcommands, or Python modules.
2. Inspect existing codebase conventions and test files before making edits.
3. Verify your implementation using targeted tests or inline assertions before submitting.
4. Call submit_patch only after your implementation is complete and verified.
5. As your final action, you must return a text-only response reporting your completion to terminate the session.
## Workspace Layout
The repository is located at `/workspace`. Here is the directory tree (up to 3 levels):
```
./CHANGELOG.txt
./CODE_OF_CONDUCT.md
./Dockerfile
./LICENSE
./Makefile
./README.md
./docs
./examples
./mypy.ini
./pyais
./pyais/__init__.py
./pyais/ais_types.py
./pyais/constants.py
./pyais/decode.py
./pyais/encode.py
./pyais/exceptions.py
./pyais/filter.py
./pyais/main.py
./pyais/messages.py
./pyais/py.typed
./pyais/stream.py
./pyais/tracker.py
./pyais/util.py
./pyproject.toml
./requirements.txt
./tests
```
Turn 1at 24 s242 tokens out3,064 prompt tokens
The user is reporting a bug in how communication status (SOTDMA vs ITDMA) is determined in `pyais`.
The current implementation checks if the `radio` value (which is the communication state) is greater than `MAX_COMM_STATE_VALUE` (which corresponds to the 20th bit).
However, the problem statement says the communication state is only 19 bits long for most messages, so checking the 20th bit actually checks a bit from a previous field (e.g., RAIM flag in types 1, 2, 3).

The suggested solution is to use the message type to determine the communication status type, except for types 9, 18, and 26, where the existing bit-based logic (which presumably works because of how those messages are structured) should be kept.

Let's start by finding where `is_sotdma` and `is_itdma` are defined.
TOOL CALL · run_command
{
  "command": "grep -rn --include=\"*.py\" \"is_sotdma\" pyais/"
}
Result of run_command 166 characters
{
  "status": "ok",
  "stdout": "pyais/messages.py:819:        if self.is_sotdma:\npyais/messages.py:827:    def is_sotdma(self) -> bool:\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 242 tokens · stopped: STOP
<|channel>thought The user is reporting a bug in how communication status (SOTDMA vs ITDMA) is determined in `pyais`. The current implementation checks if the `radio` value (which is the communication state) is greater than `MAX_COMM_STATE_VALUE` (which corresponds to the 20th bit). However, the problem statement says the communication state is only 19 bits long for most messages, so checking the 20th bit actually checks a bit from a previous field (e.g., RAIM flag in types 1, 2, 3). The suggested solution is to use the message type to determine the communication status type, except for types 9, 18, and 26, where the existing bit-based logic (which presumably works because of how those messages are structured) should be kept. Let's start by finding where `is_sotdma` and `is_itdma` are defined.<channel|><|tool_call>call:run_command{command:<|"|>grep -rn --include="*.py" "is_sotdma" pyais/<|"|>}<tool_call|><|tool_response>
Turn 2at 30 s84 tokens out3,393 prompt tokens
Okay, it's in `pyais/messages.py`. Let's examine the code around `is_sotdma` and `is_itdma` in `pyais/messages.py`.
TOOL CALL · read_file
{
  "end_line": 840,
  "filepath": "pyais/messages.py",
  "start_line": 820
}
Result of read_file 944 characters
{
  "status": "ok",
  "filepath": "pyais/messages.py",
  "content": "            result.update(get_sotdma_comm_state(self.communication_state_raw))\n        else:\n            result.update(get_itdma_comm_state(self.communication_state_raw))\n\n        return result\n\n    @property\n    def is_sotdma(self) -> bool:\n        \"\"\"The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits\"\"\"\n        return self.radio <= self.MAX_COMM_STATE_VALUE\n\n    @property\n    def is_itdma(self) -> bool:\n        \"\"\"The radio status field has it's 20th bit (MSB) set to 1\"\"\"\n        return self.radio > self.MAX_COMM_STATE_VALUE\n\n    @property\n    def communication_state_raw(self) -> int:\n        \"\"\"Get the raw radio status except 20th bit - if present\"\"\"\n        try:\n            return self.radio & self.MAX_COMM_STATE_VALUE",
  "start_line": 820,
  "end_line": 840,
  "total_lines": 1736,
  "is_truncated": false
}
Generated tokens 84 tokens · stopped: STOP
Okay, it's in `pyais/messages.py`. Let's examine the code around `is_sotdma` and `is_itdma` in `pyais/messages.py`. <channel|><|tool_call>call:read_file{filepath:<|"|>pyais/messages.py<|"|>,start_line:820,end_line:840}<tool_call|><|tool_response>
Turn 3at 44 s174 tokens out3,817 prompt tokens
Wait, `is_sotdma` and `is_itdma` are properties of a class. I need to know which class they belong to. Since they are at line 827, let's look further up to find the class definition.
Wait, the `read_file` output says `pyais/messages.py` line 820-840.
Actually, let's look at the class they are in.
TOOL CALL · run_command
{
  "command": "grep -n \"class \" pyais/messages.py | grep -B 1 -A 1 \"def is_sotdma\" || grep -B 100 \"def is_sotdma\" pyais/messages.py | grep \"class \""
}
Result of run_command 170 characters
{
  "status": "ok",
  "stdout": "class CommunicationStateMixin:\n    Mixin class to access Communication State values by applicable messages.\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 174 tokens · stopped: STOP
Wait, `is_sotdma` and `is_itdma` are properties of a class. I need to know which class they belong to. Since they are at line 827, let's look further up to find the class definition. Wait, the `read_file` output says `pyais/messages.py` line 820-840. Actually, let's look at the class they are in. <channel|><|tool_call>call:run_command{command:<|"|>grep -n "class " pyais/messages.py | grep -B 1 -A 1 "def is_sotdma" || grep -B 100 "def is_sotdma" pyais/messages.py | grep "class "<|"|>}<tool_call|><|tool_response>
Turn 4at 47 s61 tokens out4,048 prompt tokens
It looks like they are in `CommunicationStateMixin`. Let's find the `CommunicationStateMixin` class and see how it's used.
TOOL CALL · run_command
{
  "command": "grep -n \"class CommunicationStateMixin\" pyais/messages.py"
}
Result of run_command 96 characters
{
  "status": "ok",
  "stdout": "792:class CommunicationStateMixin:\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 61 tokens · stopped: STOP
It looks like they are in `CommunicationStateMixin`. Let's find the `CommunicationStateMixin` class and see how it's used. <channel|><|tool_call>call:run_command{command:<|"|>grep -n "class CommunicationStateMixin" pyais/messages.py<|"|>}<tool_call|><|tool_response>
Turn 5at 53 s71 tokens out4,157 prompt tokens
Now let's read `CommunicationStateMixin` and see what `self.radio` is and where `MAX_COMM_STATE_VALUE` is defined.
TOOL CALL · read_file
{
  "end_line": 840,
  "filepath": "pyais/messages.py",
  "start_line": 792
}
Result of read_file 1,889 characters
{
  "status": "ok",
  "filepath": "pyais/messages.py",
  "content": "class CommunicationStateMixin:\n    \"\"\"\n    Mixin class to access Communication State values by applicable messages.\n\n    You may refer to 3.3.7.2.1 of:\n    https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.1371-1-200108-S!!PDF-E.pdf\n    \"\"\"\n\n    radio: int  # Type hint to make mypy happy\n\n    MAX_COMM_STATE_VALUE = 0x7ffff\n\n    def get_communication_state(self) -> Dict[str, typing.Optional[int]]:\n        \"\"\"Returns information used by the slot allocation algorithm as a dict.\"\"\"\n        result: Dict[str, typing.Optional[int]] = {\n            'received_stations': None,\n            'slot_number': None,\n            'utc_hour': None,\n            'utc_minute': None,\n            'slot_offset': None,\n            'slot_timeout': None,\n            'sync_state': None,\n            'keep_flag': None,\n            'slot_increment': None,\n            'num_slots': None,\n        }\n\n        if self.is_sotdma:\n            result.update(get_sotdma_comm_state(self.communication_state_raw))\n        else:\n            result.update(get_itdma_comm_state(self.communication_state_raw))\n\n        return result\n\n    @property\n    def is_sotdma(self) -> bool:\n        \"\"\"The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits\"\"\"\n        return self.radio <= self.MAX_COMM_STATE_VALUE\n\n    @property\n    def is_itdma(self) -> bool:\n        \"\"\"The radio status field has it's 20th bit (MSB) set to 1\"\"\"\n        return self.radio > self.MAX_COMM_STATE_VALUE\n\n    @property\n    def communication_state_raw(self) -> int:\n        \"\"\"Get the raw radio status except 20th bit - if present\"\"\"\n        try:\n            return self.radio & self.MAX_COMM_STATE_VALUE",
  "start_line": 792,
  "end_line": 840,
  "total_lines": 1736,
  "is_truncated": false
}
Generated tokens 71 tokens · stopped: STOP
Now let's read `CommunicationStateMixin` and see what `self.radio` is and where `MAX_COMM_STATE_VALUE` is defined. <channel|><|tool_call>call:read_file{end_line:840,filepath:<|"|>pyais/messages.py<|"|>,start_line:792}<tool_call|><|tool_response>
Turn 6at 74 s283 tokens out4,871 prompt tokens
`MAX_COMM_STATE_VALUE = 0x7ffff` (which is 2^19 - 1, i.e., 19 ones).
So `self.radio <= 0x7ffff` means the 20th bit (bit 19, if 0-indexed) is not set.
`self.radio > 0x7ffff` means the 20th bit is set.

The problem statement says: "Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here, as with these message types the bit before the actual com state determines its type."

For other message types, we should use the message type to determine if it's SOTDMA or ITDMA.

Wait, the `CommunicationStateMixin` is used by several message classes. I need to find which ones and what their types are.

First, let's see the message types for those that use `CommunicationStateMixin`.

I'll search for classes that inherit from `CommunicationStateMixin` and check their `message_type` (or similar attribute).
TOOL CALL · run_command
{
  "command": "grep -rn \"CommunicationStateMixin\" pyais/messages.py"
}
Result of run_command 658 characters
{
  "status": "ok",
  "stdout": "792:class CommunicationStateMixin:\n848:class MessageType1(Payload, CommunicationStateMixin):\n889:class MessageType4(Payload, CommunicationStateMixin):\n996:class MessageType9(Payload, CommunicationStateMixin):\n1141:class MessageType18(Payload, CommunicationStateMixin):\n1545:class MessageType26AddressedStructured(Payload, CommunicationStateMixin):\n1560:class MessageType26BroadcastStructured(Payload, CommunicationStateMixin):\n1574:class MessageType26AddressedUnstructured(Payload, CommunicationStateMixin):\n1589:class MessageType26BroadcastUnstructured(Payload, CommunicationStateMixin):\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 283 tokens · stopped: STOP
`MAX_COMM_STATE_VALUE = 0x7ffff` (which is 2^19 - 1, i.e., 19 ones). So `self.radio <= 0x7ffff` means the 20th bit (bit 19, if 0-indexed) is not set. `self.radio > 0x7ffff` means the 20th bit is set. The problem statement says: "Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here, as with these message types the bit before the actual com state determines its type." For other message types, we should use the message type to determine if it's SOTDMA or ITDMA. Wait, the `CommunicationStateMixin` is used by several message classes. I need to find which ones and what their types are. First, let's see the message types for those that use `CommunicationStateMixin`. I'll search for classes that inherit from `CommunicationStateMixin` and check their `message_type` (or similar attribute). <channel|><|tool_call>call:run_command{command:<|"|>grep -rn "CommunicationStateMixin" pyais/messages.py<|"|>}<tool_call|><|tool_response>
Turn 7at 92 s303 tokens out5,352 prompt tokens
So we have:
- Type 1
- Type 4
- Type 9 (Edge case)
- Type 18 (Edge case)
- Type 26 (Edge case)

According to the problem description: "Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here, as with these message types the bit before the actual com state determines its type."

Wait, the description says "Table 46 of the M.1371-5 standard shows which type uses which com state."

I don't have the standard, but the description implies that for types other than 9, 18, 26, the communication state type is fixed based on the message type.

Let's look at the messages that use `CommunicationStateMixin` again.
Types: 1, 4, 9, 18, 26.

The user says "In the case of a type 1, 2 or 3 message, it would be checked whether the RAIM flag is set."
Wait, the `grep` for `CommunicationStateMixin` didn't show Type 2 or 3. Let me check again.
TOOL CALL · run_command
{
  "command": "grep -rn \"CommunicationStateMixin\" pyais/messages.py"
}
Result of run_command 658 characters
{
  "status": "ok",
  "stdout": "792:class CommunicationStateMixin:\n848:class MessageType1(Payload, CommunicationStateMixin):\n889:class MessageType4(Payload, CommunicationStateMixin):\n996:class MessageType9(Payload, CommunicationStateMixin):\n1141:class MessageType18(Payload, CommunicationStateMixin):\n1545:class MessageType26AddressedStructured(Payload, CommunicationStateMixin):\n1560:class MessageType26BroadcastStructured(Payload, CommunicationStateMixin):\n1574:class MessageType26AddressedUnstructured(Payload, CommunicationStateMixin):\n1589:class MessageType26BroadcastUnstructured(Payload, CommunicationStateMixin):\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 303 tokens · stopped: STOP
So we have: - Type 1 - Type 4 - Type 9 (Edge case) - Type 18 (Edge case) - Type 26 (Edge case) According to the problem description: "Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here, as with these message types the bit before the actual com state determines its type." Wait, the description says "Table 46 of the M.1371-5 standard shows which type uses which com state." I don't have the standard, but the description implies that for types other than 9, 18, 26, the communication state type is fixed based on the message type. Let's look at the messages that use `CommunicationStateMixin` again. Types: 1, 4, 9, 18, 26. The user says "In the case of a type 1, 2 or 3 message, it would be checked whether the RAIM flag is set." Wait, the `grep` for `CommunicationStateMixin` didn't show Type 2 or 3. Let me check again. <channel|><|tool_call>call:run_command{command:<|"|>grep -rn "CommunicationStateMixin" pyais/messages.py<|"|>}<tool_call|><|tool_response>
Turn 8at 106 s299 tokens out5,853 prompt tokens
Hmm, only 1, 4, 9, 18, 26. Maybe Type 2 and 3 don't use `CommunicationStateMixin`? Or maybe they are defined differently.
Actually, Type 1, 2, 3 are usually SOTDMA (Position Report).

Wait, the user provided a reproduction example:
```python
decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')
com = decoded.get_communication_state()
print(f"SOTDMA: {decoded.is_sotdma}, ITDMA: {decoded.is_itdma}")
```

Let's run this reproduction to see what it gives.
TOOL CALL · run_command
{
  "command": "python3 - <<'PY'\nimport pyais\ndecoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')\ncom = decoded.get_communication_state()\nprint(f\"SOTDMA: {decoded.is_sotdma}, ITDMA: {decoded.is_itdma}\")\nPY\n"
}
Result of run_command 88 characters
{
  "status": "ok",
  "stdout": "SOTDMA: True, ITDMA: False\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 299 tokens · stopped: STOP
Hmm, only 1, 4, 9, 18, 26. Maybe Type 2 and 3 don't use `CommunicationStateMixin`? Or maybe they are defined differently. Actually, Type 1, 2, 3 are usually SOTDMA (Position Report). Wait, the user provided a reproduction example: ```python decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51') com = decoded.get_communication_state() print(f"SOTDMA: {decoded.is_sotdma}, ITDMA: {decoded.is_itdma}") ``` Let's run this reproduction to see what it gives. <channel|><|tool_call>call:run_command{command:<|"|>python3 - <<'PY' import pyais decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51') com = decoded.get_communication_state() print(f"SOTDMA: {decoded.is_sotdma}, ITDMA: {decoded.is_itdma}") PY <|"|>}<tool_call|><|tool_response>
Turn 9at 128 s361 tokens out6,202 prompt tokens
The output says `SOTDMA: True, ITDMA: False`.
The problem states "incorrectly indicates that it is a SOTDMA communication status, although it is an ITDMA."
So it should be `SOTDMA: False, ITDMA: True`.

Wait, if it's ITDMA, `is_itdma` should be `True` and `is_sotdma` should be `False`.

The current logic is:
```python
    @property
    def is_sotdma(self) -> bool:
        return self.radio <= self.MAX_COMM_STATE_VALUE

    @property
    def is_itdma(self) -> bool:
        return self.radio > self.MAX_COMM_STATE_VALUE
```
In the reproduction, `decoded.radio` must be `<= 0x7ffff`.

The user says that for Type 1, 2, 3, the 20th bit (which is being checked by `self.radio > 0x7ffff`) is actually the RAIM flag, not the communication status.
Wait, the RAIM flag is part of the message payload, but `self.radio` is supposedly the communication state.

If `MessageType1` inherits from `CommunicationStateMixin`, then `self.radio` will be whatever is assigned to the `radio` attribute of `MessageType1`.

Let's look at `MessageType1` in `pyais/messages.py`.
TOOL CALL · read_file
{
  "end_line": 860,
  "filepath": "pyais/messages.py",
  "start_line": 848
}
Result of read_file 1,077 characters
{
  "status": "ok",
  "filepath": "pyais/messages.py",
  "content": "class MessageType1(Payload, CommunicationStateMixin):\n    \"\"\"\n    AIS Vessel position report using SOTDMA (Self-Organizing Time Division Multiple Access)\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_types_1_2_and_3_position_report_class_a\n    \"\"\"\n    msg_type = bit_field(6, int, default=1, signed=False)\n    repeat = bit_field(2, int, default=0, signed=False)\n    mmsi = bit_field(30, int, from_converter=from_mmsi)\n    status = bit_field(4, int, default=0, converter=NavigationStatus.from_value, signed=False)\n    turn = bit_field(8, float, default=TurnRate.NO_TI_DEFAULT, signed=True, to_converter=to_turn, from_converter=from_turn)\n    speed = bit_field(10, float, from_converter=from_speed, to_converter=to_speed, default=0, signed=False)\n    accuracy = bit_field(1, bool, default=0, signed=False)\n    lon = bit_field(28, float, from_converter=from_lat_lon, to_converter=to_lat_lon, default=0, signed=True)",
  "start_line": 848,
  "end_line": 860,
  "total_lines": 1736,
  "is_truncated": false
}
Generated tokens 361 tokens · stopped: STOP
The output says `SOTDMA: True, ITDMA: False`. The problem states "incorrectly indicates that it is a SOTDMA communication status, although it is an ITDMA." So it should be `SOTDMA: False, ITDMA: True`. Wait, if it's ITDMA, `is_itdma` should be `True` and `is_sotdma` should be `False`. The current logic is: ```python @property def is_sotdma(self) -> bool: return self.radio <= self.MAX_COMM_STATE_VALUE @property def is_itdma(self) -> bool: return self.radio > self.MAX_COMM_STATE_VALUE ``` In the reproduction, `decoded.radio` must be `<= 0x7ffff`. The user says that for Type 1, 2, 3, the 20th bit (which is being checked by `self.radio > 0x7ffff`) is actually the RAIM flag, not the communication status. Wait, the RAIM flag is part of the message payload, but `self.radio` is supposedly the communication state. If `MessageType1` inherits from `CommunicationStateMixin`, then `self.radio` will be whatever is assigned to the `radio` attribute of `MessageType1`. Let's look at `MessageType1` in `pyais/messages.py`. <channel|><|tool_call>call:read_file{end_line:860,filepath:<|"|>pyais/messages.py<|"|>,start_line:848}<tool_call|><|tool_response>
Turn 10at 135 s88 tokens out6,956 prompt tokens
I don't see `radio` being defined in `MessageType1`. Where is `radio` coming from?
Wait, `Payload` might be doing something or there might be more fields. Let's read more of `MessageType1`.
TOOL CALL · read_file
{
  "end_line": 900,
  "filepath": "pyais/messages.py",
  "start_line": 861
}
Result of read_file 2,043 characters
{
  "status": "ok",
  "filepath": "pyais/messages.py",
  "content": "    lat = bit_field(27, float, from_converter=from_lat_lon, to_converter=to_lat_lon, default=0, signed=True)\n    course = bit_field(12, float, from_converter=from_10th, to_converter=to_10th, default=0, signed=False)\n    heading = bit_field(9, int, default=0, signed=False)\n    second = bit_field(6, int, default=0, signed=False)\n    maneuver = bit_field(2, int, default=0, from_converter=ManeuverIndicator.from_value,\n                         to_converter=ManeuverIndicator.from_value, signed=False)\n    spare_1 = bit_field(3, bytes, default=b'')\n    raim = bit_field(1, bool, default=0)\n    radio = bit_field(19, int, default=0, signed=False)\n\n\nclass MessageType2(MessageType1):\n    \"\"\"\n    AIS Vessel position report using SOTDMA (Self-Organizing Time Division Multiple Access)\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_types_1_2_and_3_position_report_class_a\n    \"\"\"\n    msg_type = bit_field(6, int, default=2)\n\n\nclass MessageType3(MessageType1):\n    \"\"\"\n    AIS Vessel position report using ITDMA (Incremental Time Division Multiple Access)\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_types_1_2_and_3_position_report_class_a\n    \"\"\"\n    msg_type = bit_field(6, int, default=3)\n\n\n@attr.s(slots=True)\nclass MessageType4(Payload, CommunicationStateMixin):\n    \"\"\"\n    AIS Vessel position report using SOTDMA (Self-Organizing Time Division Multiple Access)\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_4_base_station_report\n    \"\"\"\n    msg_type = bit_field(6, int, default=4, signed=False)\n    repeat = bit_field(2, int, default=0, signed=False)\n    mmsi = bit_field(30, int, from_converter=from_mmsi)\n    year = bit_field(14, int, default=1970, signed=False)\n    month = bit_field(4, int, default=1, signed=False)\n    day = bit_field(5, int, default=1, signed=False)\n    hour = bit_field(5, int, default=0, signed=False)",
  "start_line": 861,
  "end_line": 900,
  "total_lines": 1736,
  "is_truncated": false
}
Generated tokens 88 tokens · stopped: STOP
I don't see `radio` being defined in `MessageType1`. Where is `radio` coming from? Wait, `Payload` might be doing something or there might be more fields. Let's read more of `MessageType1`. <channel|><|tool_call>call:read_file{end_line:900,filepath:<|"|>pyais/messages.py<|"|>,start_line:861}<tool_call|><|tool_response>
Turn 11at 184 s755 tokens out7,787 prompt tokens
Okay, so `radio = bit_field(19, int, default=0, signed=False)` is defined in `MessageType1`.
The current `is_sotdma` and `is_itdma` implementation in `CommunicationStateMixin` uses `self.radio`.

Wait, if `radio` is a `bit_field(19, ...)`, then `self.radio` will always be in range [0, 2^19-1], which is [0, 0x7ffff].
So `self.radio <= 0x7ffff` will ALWAYS be True.
And `self.radio > 0x7ffff` will ALWAYS be False.
This explains why `is_sotdma` is always True for `MessageType1`.

The problem description says: "According to the following code it is checked whether the 20th bit of the communications status is set or not. However, as the com state is only 19 bits long, a bit from the previous data fields is checked here and not one from the com state."

Wait, if `self.radio` is coming from `bit_field(19, ...)`, it should only have 19 bits.
Where is the "20th bit" coming from?
The user provided:
```python
    @property
    def is_sotdma(self) -> bool:
        """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits"""
        return self.radio <= self.MAX_COMM_STATE_VALUE

    @property
    def is_itdma(self) -> bool:
        """The radio status field has it's 20th bit (MSB) set to 1"""
        return self.radio > self.MAX_COMM_STATE_VALUE
```
If `self.radio` is an `int` and it's always <= 0x7ffff, then `is_sotdma` is always True.

Wait, the user's comment in the problem statement says: "In the case of a type 1, 2 or 3 message, it would be checked whether the RAIM flag is set."

RAIM flag is defined just before `radio` in `MessageType1`:
```python
    raim = bit_field(1, bool, default=0)
    radio = bit_field(19, int, default=0, signed=False)
```

If the decoding mechanism somehow combined `raim` and `radio` into the `radio` attribute when accessing it? No, that shouldn't happen with `bit_field`.
Wait, let me look at how `bit_field` is implemented. But wait, maybe the way `Payload` or the decoding logic works is that it's actually reading more bits than specified if not careful? No, that's unlikely.

Let's look at the `CommunicationStateMixin`'s `is_sotdma` and `is_itdma` again.
They use `self.radio`.
In `MessageType1`, `radio` is 19 bits.
In `MessageType9`, what is `radio`?

Let's check `MessageType9`.
TOOL CALL · read_file
{
  "end_line": 1010,
  "filepath": "pyais/messages.py",
  "start_line": 996
}
Result of read_file 981 characters
{
  "status": "ok",
  "filepath": "pyais/messages.py",
  "content": "class MessageType9(Payload, CommunicationStateMixin):\n    \"\"\"\n    Standard SAR Aircraft Position Report\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_9_standard_sar_aircraft_position_report\n    \"\"\"\n    msg_type = bit_field(6, int, default=9, signed=False)\n    repeat = bit_field(2, int, default=0, signed=False)\n    mmsi = bit_field(30, int, from_converter=from_mmsi)\n\n    alt = bit_field(12, int, default=0, signed=False)\n    # speed over ground is in knots, not deciknots\n    speed = bit_field(10, float, default=0, signed=False)\n    accuracy = bit_field(1, bool, default=0, signed=False)\n    lon = bit_field(28, float, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)\n    lat = bit_field(27, float, from_converter=from_lat_lon, to_converter=to_lat_lon, signed=True, default=0)",
  "start_line": 996,
  "end_line": 1010,
  "total_lines": 1736,
  "is_truncated": false
}
Generated tokens 755 tokens · stopped: STOP
Okay, so `radio = bit_field(19, int, default=0, signed=False)` is defined in `MessageType1`. The current `is_sotdma` and `is_itdma` implementation in `CommunicationStateMixin` uses `self.radio`. Wait, if `radio` is a `bit_field(19, ...)`, then `self.radio` will always be in range [0, 2^19-1], which is [0, 0x7ffff]. So `self.radio <= 0x7ffff` will ALWAYS be True. And `self.radio > 0x7ffff` will ALWAYS be False. This explains why `is_sotdma` is always True for `MessageType1`. The problem description says: "According to the following code it is checked whether the 20th bit of the communications status is set or not. However, as the com state is only 19 bits long, a bit from the previous data fields is checked here and not one from the com state." Wait, if `self.radio` is coming from `bit_field(19, ...)`, it should only have 19 bits. Where is the "20th bit" coming from? The user provided: ```python @property def is_sotdma(self) -> bool: """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits""" return self.radio <= self.MAX_COMM_STATE_VALUE @property def is_itdma(self) -> bool: """The radio status field has it's 20th bit (MSB) set to 1""" return self.radio > self.MAX_COMM_STATE_VALUE ``` If `self.radio` is an `int` and it's always <= 0x7ffff, then `is_sotdma` is always True. Wait, the user's comment in the problem statement says: "In the case of a type 1, 2 or 3 message, it would be checked whether the RAIM flag is set." RAIM flag is defined just before `radio` in `MessageType1`: ```python raim = bit_field(1, bool, default=0) radio = bit_field(19, int, default=0, signed=False) ``` If the decoding mechanism somehow combined `raim` and `radio` into the `radio` attribute when accessing it? No, that shouldn't happen with `bit_field`. Wait, let me look at how `bit_field` is implemented. But wait, maybe the way `Payload` or the decoding logic works is that it's actually reading more bits than specified if not careful? No, that's unlikely. Let's look at the `CommunicationStateMixin`'s `is_sotdma` and `is_itdma` again. They use `self.radio`. In `MessageType1`, `radio` is 19 bits. In `MessageType9`, what is `radio`? Let's check `MessageType9`.<channel|><|tool_call>call:read_file{end_line:1010,filepath:<|"|>pyais/messages.py<|"|>,start_line:996}<tool_call|><|tool_response>
Turn 12at 192 s113 tokens out8,913 prompt tokens
Wait, `MessageType9` also doesn't have `radio` defined in the snippet. Let's read more of it.
Actually, `MessageType9` probably defines `radio` later.

Wait, I see `MessageType4` also didn't have `radio` in the first 10 lines. Let's see where `radio` is defined for `MessageType4`.
TOOL CALL · run_command
{
  "command": "grep -n \"radio = bit_field\" pyais/messages.py"
}
Result of run_command 540 characters
{
  "status": "ok",
  "stdout": "869:    radio = bit_field(19, int, default=0, signed=False)\n910:    radio = bit_field(19, int, default=0, signed=False)\n1019:    radio = bit_field(20, int, default=0, signed=False)\n1166:    radio = bit_field(20, int, default=0)\n1556:    radio = bit_field(20, int, default=0, signed=False)\n1570:    radio = bit_field(20, int, default=0, signed=False)\n1585:    radio = bit_field(20, int, default=0, signed=False)\n1598:    radio = bit_field(20, int, default=0, signed=False)\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 113 tokens · stopped: STOP
Wait, `MessageType9` also doesn't have `radio` defined in the snippet. Let's read more of it. Actually, `MessageType9` probably defines `radio` later. Wait, I see `MessageType4` also didn't have `radio` in the first 10 lines. Let's see where `radio` is defined for `MessageType4`. <channel|><|tool_call>call:run_command{command:<|"|>grep -n "radio = bit_field" pyais/messages.py<|"|>}<tool_call|><|tool_response>
Turn 13at 272 s1,407 tokens out9,266 prompt tokens
Aha!
- Line 869 (MessageType1): `radio = bit_field(19, ...)`
- Line 910 (MessageType4): `radio = bit_field(19, ...)`
- Line 1019 (MessageType9): `radio = bit_field(20, ...)`
- Line 1166 (MessageType18): `radio = bit_field(20, ...)`
- Line 1556 (MessageType26...): `radio = bit_field(20, ...)`
- Line 1570 (MessageType26...): `radio = bit_field(20, ...)`
- Line 1585 (MessageType26...): `radio = bit_field(20, ...)`
- Line 1598 (MessageType26...): `radio = bit_field(20, ...)`

So for Type 1 and Type 4, `radio` is defined as 19 bits.
For Type 9, 18, and 26, `radio` is defined as 20 bits.

This perfectly matches what the user said: "as the com state is only 19 bits long, a bit from the previous data fields is checked here and not one from the com state."

Wait, if it's defined as `bit_field(19, ...)`, then `self.radio` will always be <= 0x7ffff.
But why did the user say it's checking the RAIM flag?
RAIM flag is the bit before `radio` in `MessageType1`.
If `radio` is 19 bits, and we check if `self.radio > 0x7ffff` (which is the 20th bit), it should always be False.

Wait, let me re-read the problem: "Since 1 is the default value, pyais returns in almost all cases that it is a SOTDMA com state."
`is_sotdma` returns `self.radio <= self.MAX_COMM_STATE_VALUE`.
If `self.radio` is 19 bits, it is ALWAYS `<= 0x7ffff`. So `is_sotdma` is ALWAYS True.
The user says "Since 1 is the default value, pyais returns in almost all cases that it is a SOTDMA com state."
I suspect they mean the RAIM flag's default value or something, but actually, it's just that `self.radio` is 19 bits, so it's always <= 0x7ffff.

Anyway, the solution is to use the message type.

According to the problem statement:
"It is better to use the message type rather than the bit length to indicate the type of communication status."
"Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here, as with these message types the bit before the actual com state determines its type."

So for Type 9, 18, 26: keep current logic (which uses `self.radio > 0x7ffff`).
For other types: use message type.

Which types use which communication state?
Table 46 of the M.1371-5 standard. I don't have it.
But looking at the classes:
- `MessageType1`: "AIS Vessel position report using SOTDMA"
- `MessageType2`: inherits from `MessageType1` (implies SOTDMA)
- `MessageType3`: "AIS Vessel position report using ITDMA"
- `MessageType4`: "AIS Vessel position report using SOTDMA"

Wait, `MessageType3` says ITDMA!
Let's look at `MessageType3` again.
```python
class MessageType3(MessageType1):
    """
    AIS Vessel position report using ITDMA (Incremental Time Division Multiple Access)
    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_types_1_2_and_3_position_report_class_a
    """
    msg_type = bit_field(6, int, default=3)
```
Since `MessageType3` inherits from `MessageType1`, its `radio` field is also 19 bits.
So for `MessageType3`, `is_sotdma` will also be True (because `self.radio <= 0x7ffff` is always true), but it SHOULD be False because Type 3 is ITDMA.

So the logic should be:
- If type is 1, 2, 4: SOTDMA
- If type is 3: ITDMA
- If type is 9, 18, 26: check `self.radio > 0x7ffff`
- For any other type that uses `CommunicationStateMixin`: ? (I should probably check if there are others).

Wait, let's see if there are other classes using `CommunicationStateMixin`.
I already did: Types 1, 4, 9, 18, 26. (And Type 2 and 3 via inheritance).

So:
Type 1 -> SOTDMA
Type 2 -> SOTDMA
Type 3 -> ITDMA
Type 4 -> SOTDMA
Type 9 -> Dynamic (check bit)
Type 18 -> Dynamic (check bit)
Type 26 -> Dynamic (check bit)

Let's verify this logic.
For `MessageType3`, `is_sotdma` should be False, `is_itdma` should be True.

Let's test this with a Type 3 message.
The reproduction message: `!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51`
Let's find out its type.
The decoded object:
TOOL CALL · run_command
{
  "command": "python3 - <<'PY'\nimport pyais\ndecoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')\nprint(f\"Type: {decoded.msg_type}\")\nPY\n"
}
Result of run_command 69 characters
{
  "status": "ok",
  "stdout": "Type: 3\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 1,407 tokens · stopped: STOP
Aha! - Line 869 (MessageType1): `radio = bit_field(19, ...)` - Line 910 (MessageType4): `radio = bit_field(19, ...)` - Line 1019 (MessageType9): `radio = bit_field(20, ...)` - Line 1166 (MessageType18): `radio = bit_field(20, ...)` - Line 1556 (MessageType26...): `radio = bit_field(20, ...)` - Line 1570 (MessageType26...): `radio = bit_field(20, ...)` - Line 1585 (MessageType26...): `radio = bit_field(20, ...)` - Line 1598 (MessageType26...): `radio = bit_field(20, ...)` So for Type 1 and Type 4, `radio` is defined as 19 bits. For Type 9, 18, and 26, `radio` is defined as 20 bits. This perfectly matches what the user said: "as the com state is only 19 bits long, a bit from the previous data fields is checked here and not one from the com state." Wait, if it's defined as `bit_field(19, ...)`, then `self.radio` will always be <= 0x7ffff. But why did the user say it's checking the RAIM flag? RAIM flag is the bit before `radio` in `MessageType1`. If `radio` is 19 bits, and we check if `self.radio > 0x7ffff` (which is the 20th bit), it should always be False. Wait, let me re-read the problem: "Since 1 is the default value, pyais returns in almost all cases that it is a SOTDMA com state." `is_sotdma` returns `self.radio <= self.MAX_COMM_STATE_VALUE`. If `self.radio` is 19 bits, it is ALWAYS `<= 0x7ffff`. So `is_sotdma` is ALWAYS True. The user says "Since 1 is the default value, pyais returns in almost all cases that it is a SOTDMA com state." I suspect they mean the RAIM flag's default value or something, but actually, it's just that `self.radio` is 19 bits, so it's always <= 0x7ffff. Anyway, the solution is to use the message type. According to the problem statement: "It is better to use the message type rather than the bit length to indicate the type of communication status." "Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here, as with these message types the bit before the actual com state determines its type." So for Type 9, 18, 26: keep current logic (which uses `self.radio > 0x7ffff`). For other types: use message type. Which types use which communication state? Table 46 of the M.1371-5 standard. I don't have it. But looking at the classes: - `MessageType1`: "AIS Vessel position report using SOTDMA" - `MessageType2`: inherits from `MessageType1` (implies SOTDMA) - `MessageType3`: "AIS Vessel position report using ITDMA" - `MessageType4`: "AIS Vessel position report using SOTDMA" Wait, `MessageType3` says ITDMA! Let's look at `MessageType3` again. ```python class MessageType3(MessageType1): """ AIS Vessel position report using ITDMA (Incremental Time Division Multiple Access) Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_types_1_2_and_3_position_report_class_a """ msg_type = bit_field(6, int, default=3) ``` Since `MessageType3` inherits from `MessageType1`, its `radio` field is also 19 bits. So for `MessageType3`, `is_sotdma` will also be True (because `self.radio <= 0x7ffff` is always true), but it SHOULD be False because Type 3 is ITDMA. So the logic should be: - If type is 1, 2, 4: SOTDMA - If type is 3: ITDMA - If type is 9, 18, 26: check `self.radio > 0x7ffff` - For any other type that uses `CommunicationStateMixin`: ? (I should probably check if there are others). Wait, let's see if there are other classes using `CommunicationStateMixin`. I already did: Types 1, 4, 9, 18, 26. (And Type 2 and 3 via inheritance). So: Type 1 -> SOTDMA Type 2 -> SOTDMA Type 3 -> ITDMA Type 4 -> SOTDMA Type 9 -> Dynamic (check bit) Type 18 -> Dynamic (check bit) Type 26 -> Dynamic (check bit) Let's verify this logic. For `MessageType3`, `is_sotdma` should be False, `is_itdma` should be True. Let's test this with a Type 3 message. The reproduction message: `!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51` Let's find out its type. The decoded object: <channel|><|tool_call>call:run_command{command:<|"|>python3 - <<'PY' import pyais decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51') print(f"Type: {decoded.msg_type}") PY <|"|>}<tool_call|><|tool_response>
Turn 14at 353 s1,458 tokens out10,717 prompt tokens
Yes, it's Type 3. And for Type 3, it's ITDMA.
But the current output was `SOTDMA: True, ITDMA: False`.
So my analysis is correct.

Now I need to implement the fix in `CommunicationStateMixin`.
I need access to the message type.
In `pyais/messages.py`, all these messages have a `msg_type` attribute (via `bit_field`).

Wait, `CommunicationStateMixin` is a mixin. Does it have access to `self.msg_type`?
Yes, because it's mixed into classes that have `msg_type`.

So the implementation should look like this:

```python
    @property
    def is_sotdma(self) -> bool:
        if self.msg_type in (9, 18, 26):
            return self.radio <= self.MAX_COMM_STATE_VALUE
        if self.msg_type == 3:
            return False
        return True # For 1, 2, 4
```

Wait, what if there are other types in the future?
The problem says: "Table 46 of the M.1371-5 standard shows which type uses which com state."
Maybe I should explicitly list types for SOTDMA and ITDMA.
Type 1, 2, 4 -> SOTDMA
Type 3 -> ITDMA
Type 9, 18, 26 -> Dynamic

Are there any others? I should check if `CommunicationStateMixin` is used elsewhere.
I already did `grep -rn "CommunicationStateMixin" pyais/messages.py` and it found 1, 4, 9, 18, 26.

Let's check if there are other types that might be added later or missed.
If I use:
```python
    @property
    def is_sotdma(self) -> bool:
        if self.msg_type in (9, 18, 26):
            return self.radio <= self.MAX_COMM_STATE_VALUE
        return self.msg_type != 3
```
Wait, this assumes everything other than 3, 9, 18, 26 is SOTDMA.

Let's be more explicit.
SOTDMA: 1, 2, 4.
ITDMA: 3.
Dynamic: 9, 18, 26.

Wait, what if `msg_type` is not one of these?
Probably it's safer to use the logic:
If 9, 18, 26 -> use bit.
If 3 -> False.
Otherwise -> True.

Actually, the problem says: "It is better to use the message type rather than the bit length to indicate the type of communication status."

Let's implement this.

One detail: `is_itdma` should be the inverse of `is_sotdma`.

```python
    @property
    def is_sotdma(self) -> bool:
        if self.msg_type in (9, 18, 26):
            return self.radio <= self.MAX_COMM_STATE_VALUE
        return self.msg_type != 3

    @property
    def is_itdma(self) -> bool:
        return not self.is_sotdma
```

Let's double check if `self.msg_type` is always available.
Yes, both `MessageType1` and others have `msg_type = bit_field(...)`.

Wait, what if `self.radio` is not available? But `CommunicationStateMixin` is only used by classes that have `radio`.

Wait, look at `is_itdma`'s current implementation:
```python
    @property
    def is_itdma(self) -> bool:
        """The radio status field has it's 20th bit (MSB) set to 1"""
        return self.radio > self.MAX_COMM_STATE_VALUE
```
If I change `is_sotdma` and make `is_itdma` depend on it, it should be fine.

Wait, I should make sure `is_itdma` is also correct based on the message type.

If `msg_type` is 9, 18, 26:
`is_sotdma` is `self.radio <= 0x7ffff`.
`is_itdma` is `not (self.radio <= 0x7ffff)` which is `self.radio > 0x7ffff`. Correct.

If `msg_type` is 3:
`is_sotdma` is `3 != 3` which is `False`.
`is_itdma` is `not False` which is `True`. Correct.

If `msg_type` is 1, 2, 4:
`is_sotdma` is `1 != 3` which is `True`.
`is_itdma` is `not True` which is `False`. Correct.

So this logic seems robust for the current supported types.

Let's check if there are any other properties in `CommunicationStateMixin` that depend on `is_sotdma` or `is_itdma`.
`get_communication_state` uses them.

```python
    def get_communication_state(self) -> Dict[str, typing.Optional[int]]:
        # ...
        if self.is_sotdma:
            result.update(get_sotdma_comm_state(self.communication_state_raw))
        else:
            result.update(get_itdma_comm_state(self.communication_state_raw))
        return result
```

And `communication_state_raw`:
```python
    @property
    def communication_state_raw(self) -> int:
        """Get the raw radio status except 20th bit - if present"""
        try:
            return self.radio & self.MAX_COMM_STATE_VALUE
```
Wait, the `try:` in `communication_state_raw` is incomplete in the `read_file` output. Let me read it again.
TOOL CALL · read_file
{
  "end_line": 845,
  "filepath": "pyais/messages.py",
  "start_line": 835
}
Result of read_file 537 characters
{
  "status": "ok",
  "filepath": "pyais/messages.py",
  "content": "\n    @property\n    def communication_state_raw(self) -> int:\n        \"\"\"Get the raw radio status except 20th bit - if present\"\"\"\n        try:\n            return self.radio & self.MAX_COMM_STATE_VALUE\n        except AttributeError as err:\n            raise ValueError(\n                'Communication State is only available for messages with radio field'\n            ) from err\n",
  "start_line": 835,
  "end_line": 845,
  "total_lines": 1736,
  "is_truncated": false
}
Generated tokens 1,458 tokens · stopped: STOP
Yes, it's Type 3. And for Type 3, it's ITDMA. But the current output was `SOTDMA: True, ITDMA: False`. So my analysis is correct. Now I need to implement the fix in `CommunicationStateMixin`. I need access to the message type. In `pyais/messages.py`, all these messages have a `msg_type` attribute (via `bit_field`). Wait, `CommunicationStateMixin` is a mixin. Does it have access to `self.msg_type`? Yes, because it's mixed into classes that have `msg_type`. So the implementation should look like this: ```python @property def is_sotdma(self) -> bool: if self.msg_type in (9, 18, 26): return self.radio <= self.MAX_COMM_STATE_VALUE if self.msg_type == 3: return False return True # For 1, 2, 4 ``` Wait, what if there are other types in the future? The problem says: "Table 46 of the M.1371-5 standard shows which type uses which com state." Maybe I should explicitly list types for SOTDMA and ITDMA. Type 1, 2, 4 -> SOTDMA Type 3 -> ITDMA Type 9, 18, 26 -> Dynamic Are there any others? I should check if `CommunicationStateMixin` is used elsewhere. I already did `grep -rn "CommunicationStateMixin" pyais/messages.py` and it found 1, 4, 9, 18, 26. Let's check if there are other types that might be added later or missed. If I use: ```python @property def is_sotdma(self) -> bool: if self.msg_type in (9, 18, 26): return self.radio <= self.MAX_COMM_STATE_VALUE return self.msg_type != 3 ``` Wait, this assumes everything other than 3, 9, 18, 26 is SOTDMA. Let's be more explicit. SOTDMA: 1, 2, 4. ITDMA: 3. Dynamic: 9, 18, 26. Wait, what if `msg_type` is not one of these? Probably it's safer to use the logic: If 9, 18, 26 -> use bit. If 3 -> False. Otherwise -> True. Actually, the problem says: "It is better to use the message type rather than the bit length to indicate the type of communication status." Let's implement this. One detail: `is_itdma` should be the inverse of `is_sotdma`. ```python @property def is_sotdma(self) -> bool: if self.msg_type in (9, 18, 26): return self.radio <= self.MAX_COMM_STATE_VALUE return self.msg_type != 3 @property def is_itdma(self) -> bool: return not self.is_sotdma ``` Let's double check if `self.msg_type` is always available. Yes, both `MessageType1` and others have `msg_type = bit_field(...)`. Wait, what if `self.radio` is not available? But `CommunicationStateMixin` is only used by classes that have `radio`. Wait, look at `is_itdma`'s current implementation: ```python @property def is_itdma(self) -> bool: """The radio status field has it's 20th bit (MSB) set to 1""" return self.radio > self.MAX_COMM_STATE_VALUE ``` If I change `is_sotdma` and make `is_itdma` depend on it, it should be fine. Wait, I should make sure `is_itdma` is also correct based on the message type. If `msg_type` is 9, 18, 26: `is_sotdma` is `self.radio <= 0x7ffff`. `is_itdma` is `not (self.radio <= 0x7ffff)` which is `self.radio > 0x7ffff`. Correct. If `msg_type` is 3: `is_sotdma` is `3 != 3` which is `False`. `is_itdma` is `not False` which is `True`. Correct. If `msg_type` is 1, 2, 4: `is_sotdma` is `1 != 3` which is `True`. `is_itdma` is `not True` which is `False`. Correct. So this logic seems robust for the current supported types. Let's check if there are any other properties in `CommunicationStateMixin` that depend on `is_sotdma` or `is_itdma`. `get_communication_state` uses them. ```python def get_communication_state(self) -> Dict[str, typing.Optional[int]]: # ... if self.is_sotdma: result.update(get_sotdma_comm_state(self.communication_state_raw)) else: result.update(get_itdma_comm_state(self.communication_state_raw)) return result ``` And `communication_state_raw`: ```python @property def communication_state_raw(self) -> int: """Get the raw radio status except 20th bit - if present""" try: return self.radio & self.MAX_COMM_STATE_VALUE ``` Wait, the `try:` in `communication_state_raw` is incomplete in the `read_file` output. Let me read it again. <channel|><|tool_call>call:read_file{end_line:845,filepath:<|"|>pyais/messages.py<|"|>,start_line:835}<tool_call|><|tool_response>
Turn 15at 388 s646 tokens out12,345 prompt tokens
Okay, it's fine.

Now I'll apply the fix to `pyais/messages.py`.

I'll replace:
```python
    @property
    def is_sotdma(self) -> bool:
        """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits"""
        return self.radio <= self.MAX_COMM_STATE_VALUE

    @property
    def is_itdma(self) -> bool:
        """The radio status field has it's 20th bit (MSB) set to 1"""
        return self.radio > self.MAX_COMM_STATE_VALUE
```
with:
```python
    @property
    def is_sotdma(self) -> bool:
        """Determine if the communication state is SOTDMA based on message type or radio field."""
        if self.msg_type in (9, 18, 26):
            return self.radio <= self.MAX_COMM_STATE_VALUE
        return self.msg_type != 3

    @property
    def is_itdma(self) -> bool:
        """Determine if the communication state is ITDMA based on message type or radio field."""
        return not self.is_sotdma
```

Wait, let's check if `msg_type` is always available on all classes that use `CommunicationStateMixin`.
`MessageType1`, `MessageType4`, `MessageType9`, `MessageType18`, `MessageType26...` all have `msg_type = bit_field(6, ...)`.
Actually, `msg_type` is defined as a `bit_field`. In `pyais`,`bit_field` probably becomes an attribute on the instance after decoding.

Let's verify my reproduction again and check if `decoded.msg_type` is present.
Yes, I did:
```python
import pyais
decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')
print(f"Type: {decoded.msg_type}")
```
And it output `Type: 3`. So `msg_type` is available.

One more thing: should I use `self.msg_type` or `getattr(self, 'msg_type', None)`?
The mixin is only used by the `MessageTypeX` classes, all of which have `msg_type`. So `self.msg_type` should be safe.

Actually, looking at `pyais/messages.py`, most classes inherit from `Payload`.
Let's see `Payload` class.
TOOL CALL · run_command
{
  "command": "grep -n \"class Payload\" pyais/messages.py"
}
Result of run_command 89 characters
{
  "status": "ok",
  "stdout": "556:class Payload(abc.ABC):\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 646 tokens · stopped: STOP
Okay, it's fine. Now I'll apply the fix to `pyais/messages.py`. I'll replace: ```python @property def is_sotdma(self) -> bool: """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits""" return self.radio <= self.MAX_COMM_STATE_VALUE @property def is_itdma(self) -> bool: """The radio status field has it's 20th bit (MSB) set to 1""" return self.radio > self.MAX_COMM_STATE_VALUE ``` with: ```python @property def is_sotdma(self) -> bool: """Determine if the communication state is SOTDMA based on message type or radio field.""" if self.msg_type in (9, 18, 26): return self.radio <= self.MAX_COMM_STATE_VALUE return self.msg_type != 3 @property def is_itdma(self) -> bool: """Determine if the communication state is ITDMA based on message type or radio field.""" return not self.is_sotdma ``` Wait, let's check if `msg_type` is always available on all classes that use `CommunicationStateMixin`. `MessageType1`, `MessageType4`, `MessageType9`, `MessageType18`, `MessageType26...` all have `msg_type = bit_field(6, ...)`. Actually, `msg_type` is defined as a `bit_field`. In `pyais`,`bit_field` probably becomes an attribute on the instance after decoding. Let's verify my reproduction again and check if `decoded.msg_type` is present. Yes, I did: ```python import pyais decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51') print(f"Type: {decoded.msg_type}") ``` And it output `Type: 3`. So `msg_type` is available. One more thing: should I use `self.msg_type` or `getattr(self, 'msg_type', None)`? The mixin is only used by the `MessageTypeX` classes, all of which have `msg_type`. So `self.msg_type` should be safe. Actually, looking at `pyais/messages.py`, most classes inherit from `Payload`. Let's see `Payload` class. <channel|><|tool_call>call:run_command{command:<|"|>grep -n "class Payload" pyais/messages.py<|"|>}<tool_call|><|tool_response>
Turn 16at 391 s52 tokens out13,040 prompt tokens
Let's see if `Payload` has `msg_type`.
TOOL CALL · read_file
{
  "end_line": 600,
  "filepath": "pyais/messages.py",
  "start_line": 556
}
Result of read_file 1,598 characters
{
  "status": "ok",
  "filepath": "pyais/messages.py",
  "content": "class Payload(abc.ABC):\n    \"\"\"\n    Payload class\n    --------------\n    This class serves as an abstract base class for all messages.\n    Each message shall inherit from Payload and define it's set of field using the `bit_field` method.\n    \"\"\"\n\n    @staticmethod\n    def __force_type(field: typing.Any, val: typing.Any) -> typing.Any:\n        \"\"\"\n        Force a value into a specific type for a given bitfield.\n\n        Note:   This method is meant to be used with `bit_fields` only.\n        \"\"\"\n        if val is None:\n            return val\n\n        d_type = field.metadata['d_type']\n\n        if isinstance(val, d_type):\n            # The value is already of the correct type -> nothing to do\n            return val\n\n        try:\n            coerced_val = coerce_val(val, d_type)\n        except ValueError as err:\n            raise ValueError(f\"Could not coerce value for field '{field.name}'\") from err\n\n        return coerced_val\n\n    @classmethod\n    def fields(cls) -> typing.Tuple[typing.Any]:\n        \"\"\"\n        A list of all fields that were added to this class using attrs.\n        \"\"\"\n        return attr.fields(cls)  # type:ignore\n\n    def to_bitarray(self) -> bitarray:\n        \"\"\"\n        Convert all attributes of a given Payload/Message to binary.\n        \"\"\"\n        out = bitarray()\n        for field in self.fields():\n            width = field.metadata['width']",
  "start_line": 556,
  "end_line": 600,
  "total_lines": 1736,
  "is_truncated": false
}
Generated tokens 52 tokens · stopped: STOP
Let's see if `Payload` has `msg_type`. <channel|><|tool_call>call:read_file{end_line:600,filepath:<|"|>pyais/messages.py<|"|>,start_line:556}<tool_call|><|tool_response>
Turn 17at 436 s856 tokens out13,568 prompt tokens
`Payload` doesn't have `msg_type`. But all the specific message types (1, 2, 3, 4, 9, 18, 26) have it.
Since `CommunicationStateMixin` is only used by those, it should be fine.

Let's apply the change.

Wait, I should check if I should also update the docstrings to be more accurate.
Existing:
`is_sotdma`: "The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits"
`is_itdma`: "The radio status field has it's 20th bit (MSB) set to 1"

I'll update them to reflect that it now uses the message type for most cases.

Wait, let's double check if there are any other message types that might be using `CommunicationStateMixin`.
I'll run `grep -rn "CommunicationStateMixin" pyais/messages.py` again.
```
792:class CommunicationStateMixin:
848:class MessageType1(Payload, CommunicationStateMixin):
889:class MessageType4(Payload, CommunicationStateMixin):
996:class MessageType9(Payload, CommunicationStateMixin):
1141:class MessageType18(Payload, CommunicationStateMixin):
1545:class MessageType26AddressedStructured(Payload, CommunicationStateMixin):
1560:class MessageType26BroadcastStructured(Payload, CommunicationStateMixin):
1574:class MessageType26AddressedUnstructured(Payload, CommunicationStateMixin):
1589:class MessageType26BroadcastUnstructured(Payload, CommunicationStateMixin):
```
Yes, my list is complete.
Type 1, 2 (inherits 1), 3 (inherits 1), 4, 9, 18, 26.

Wait, `MessageType1` is SOTDMA.
`MessageType2` is SOTDMA.
`MessageType3` is ITDMA.
`MessageType4` is SOTDMA.
`MessageType9` isDynamic.
`MessageType18` is Dynamic.
`MessageType26` is Dynamic.

The logic:
If 9, 18, 26 -> use bit.
If 3 -> False.
Else (1, 2, 4) -> True.

This looks correct.

Now let's apply the edit.
TOOL CALL · edit_file
{
  "filepath": "pyais/messages.py",
  "new_string": "    @property\n    def is_sotdma(self) -> bool:\n        \"\"\"Determine if the communication state is SOTDMA based on message type or radio field.\"\"\"\n        if self.msg_type in (9, 18, 26):\n            return self.radio <= self.MAX_COMM_STATE_VALUE\n        return self.msg_type != 3\n\n    @property\n    def is_itdma(self) -> bool:\n        \"\"\"Determine if the communication state is ITDMA based on message type or radio field.\"\"\"\n        return not self.is_sotdma\n",
  "old_string": "    @property\n    def is_sotdma(self) -> bool:\n        \"\"\"The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits\"\"\"\n        return self.radio <= self.MAX_COMM_STATE_VALUE\n\n    @property\n    def is_itdma(self) -> bool:\n        \"\"\"The radio status field has it's 20th bit (MSB) set to 1\"\"\"\n        return self.radio > self.MAX_COMM_STATE_VALUE\n"
}
Result of edit_file 1,053 characters
{
  "status": "ok",
  "filepath": "pyais/messages.py",
  "occurrences": 1,
  "strategy": "exact",
  "diff": "--- a/pyais/messages.py\n+++ b/pyais/messages.py\n@@ -825,13 +825,15 @@\n \n     @property\n     def is_sotdma(self) -> bool:\n-        \"\"\"The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits\"\"\"\n-        return self.radio <= self.MAX_COMM_STATE_VALUE\n+        \"\"\"Determine if the communication state is SOTDMA based on message type or radio field.\"\"\"\n+        if self.msg_type in (9, 18, 26):\n+            return self.radio <= self.MAX_COMM_STATE_VALUE\n+        return self.msg_type != 3\n \n     @property\n     def is_itdma(self) -> bool:\n-        \"\"\"The radio status field has it's 20th bit (MSB) set to 1\"\"\"\n-        return self.radio > self.MAX_COMM_STATE_VALUE\n+        \"\"\"Determine if the communication state is ITDMA based on message type or radio field.\"\"\"\n+        return not self.is_sotdma\n \n     @property\n     def communication_state_raw(self) -> int:\n",
  "is_truncated": false
}
Generated tokens 856 tokens · stopped: STOP
`Payload` doesn't have `msg_type`. But all the specific message types (1, 2, 3, 4, 9, 18, 26) have it. Since `CommunicationStateMixin` is only used by those, it should be fine. Let's apply the change. Wait, I should check if I should also update the docstrings to be more accurate. Existing: `is_sotdma`: "The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits" `is_itdma`: "The radio status field has it's 20th bit (MSB) set to 1" I'll update them to reflect that it now uses the message type for most cases. Wait, let's double check if there are any other message types that might be using `CommunicationStateMixin`. I'll run `grep -rn "CommunicationStateMixin" pyais/messages.py` again. ``` 792:class CommunicationStateMixin: 848:class MessageType1(Payload, CommunicationStateMixin): 889:class MessageType4(Payload, CommunicationStateMixin): 996:class MessageType9(Payload, CommunicationStateMixin): 1141:class MessageType18(Payload, CommunicationStateMixin): 1545:class MessageType26AddressedStructured(Payload, CommunicationStateMixin): 1560:class MessageType26BroadcastStructured(Payload, CommunicationStateMixin): 1574:class MessageType26AddressedUnstructured(Payload, CommunicationStateMixin): 1589:class MessageType26BroadcastUnstructured(Payload, CommunicationStateMixin): ``` Yes, my list is complete. Type 1, 2 (inherits 1), 3 (inherits 1), 4, 9, 18, 26. Wait, `MessageType1` is SOTDMA. `MessageType2` is SOTDMA. `MessageType3` is ITDMA. `MessageType4` is SOTDMA. `MessageType9` isDynamic. `MessageType18` is Dynamic. `MessageType26` is Dynamic. The logic: If 9, 18, 26 -> use bit. If 3 -> False. Else (1, 2, 4) -> True. This looks correct. Now let's apply the edit. <channel|><|tool_call>call:edit_file{filepath:<|"|>pyais/messages.py<|"|>,new_string:<|"|> @property def is_sotdma(self) -> bool: """Determine if the communication state is SOTDMA based on message type or radio field.""" if self.msg_type in (9, 18, 26): return self.radio <= self.MAX_COMM_STATE_VALUE return self.msg_type != 3 @property def is_itdma(self) -> bool: """Determine if the communication state is ITDMA based on message type or radio field.""" return not self.is_sotdma <|"|>,old_string:<|"|> @property def is_sotdma(self) -> bool: """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits""" return self.radio <= self.MAX_COMM_STATE_VALUE @property def is_itdma(self) -> bool: """The radio status field has it's 20th bit (MSB) set to 1""" return self.radio > self.MAX_COMM_STATE_VALUE <|"|>}<tool_call|><|tool_response>
Turn 18at 440 s130 tokens out14,804 prompt tokens
Now let's verify the fix with the reproduction script.
TOOL CALL · run_command
{
  "command": "python3 - <<'PY'\nimport pyais\ndecoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')\ncom = decoded.get_communication_state()\nprint(f\"SOTDMA: {decoded.is_sotdma}, ITDMA: {decoded.is_itdma}\")\nPY\n"
}
Result of run_command 88 characters
{
  "status": "ok",
  "stdout": "SOTDMA: False, ITDMA: True\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 130 tokens · stopped: STOP
Now let's verify the fix with the reproduction script. <channel|><|tool_call>call:run_command{command:<|"|>python3 - <<'PY' import pyais decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51') com = decoded.get_communication_state() print(f"SOTDMA: {decoded.is_sotdma}, ITDMA: {decoded.is_itdma}") PY <|"|>}<tool_call|><|tool_response>

Patch, tests and logs

Final code change Included
diff --git a/pyais/messages.py b/pyais/messages.py
index 3f6ef48..6c7497a 100644
--- a/pyais/messages.py
+++ b/pyais/messages.py
@@ -825,13 +825,15 @@ class CommunicationStateMixin:
 
     @property
     def is_sotdma(self) -> bool:
-        """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits"""
-        return self.radio <= self.MAX_COMM_STATE_VALUE
+        """Determine if the communication state is SOTDMA based on message type or radio field."""
+        if self.msg_type in (9, 18, 26):
+            return self.radio <= self.MAX_COMM_STATE_VALUE
+        return self.msg_type != 3
 
     @property
     def is_itdma(self) -> bool:
-        """The radio status field has it's 20th bit (MSB) set to 1"""
-        return self.radio > self.MAX_COMM_STATE_VALUE
+        """Determine if the communication state is ITDMA based on message type or radio field."""
+        return not self.is_sotdma
 
     @property
     def communication_state_raw(self) -> int:
Test output Included
============================= test session starts ==============================
collected 302 items

tests/test_constants.py ........                                         [  2%]
tests/test_decode.py ................................................... [ 19%]
....................................................                     [ 36%]
tests/test_decode_raw.py ........                                        [ 39%]
tests/test_encode.py ................................................... [ 56%]
..........                                                               [ 59%]
tests/test_examples.py .                                                 [ 59%]
tests/test_file_stream.py .s..........                                   [ 63%]
tests/test_filters.py ...............                                    [ 68%]
tests/test_flags.py .........                                            [ 71%]
tests/test_generic_stream.py ...........                                 [ 75%]
tests/test_main.py ....                                                  [ 76%]
tests/test_nmea.py ........................                              [ 84%]
tests/test_socket.py ......                                              [ 86%]
tests/test_tag_block.py ..........                                       [ 90%]
tests/test_talker_ids.py ..........                                      [ 93%]
tests/test_tcp_stream.py ..s                                             [ 94%]
tests/test_tracker.py ................                                   [ 99%]
tests/test_udp_stream.py .                                               [100%]

==================================== PASSES ====================================
_______________________ TestExamples.test_run_every_file _______________________
----------------------------- Captured stdout call -----------------------------
MessageType1(msg_type=1, repeat=0, mmsi=367380120, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.1, accuracy=False, lon=-122.404333, lat=37.806948, course=245.2, heading=511, second=59, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=34958)
MessageType5(msg_type=5, repeat=0, mmsi=368060190, ais_version=2, imo=0, callsign='WDK4954', shipname='P/V_GOLDEN_GATE', ship_type=<ShipType.PilotVessel: 50>, to_bow=14, to_stern=7, to_port=4, to_starboard=2, epfd=<EpfdType.Internal_GNSS: 15>, month=0, day=0, hour=24, minute=60, draught=0.0, destination='', dte=False, spare_1=b'\x00')
MessageType1(msg_type=1, repeat=0, mmsi=367380120, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.1, accuracy=False, lon=-122.404333, lat=37.806948, course=245.2, heading=511, second=59, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=34958)
{'msg_type': 1, 'repeat': 0, 'mmsi': 367380120, 'status': <NavigationStatus.UnderWayUsingEngine: 0>, 'turn': <TurnRate.NO_TI_DEFAULT: -128.0>, 'speed': 0.1, 'accuracy': False, 'lon': -122.404333, 'lat': 37.806948, 'course': 245.2, 'heading': 511, 'second': 59, 'maneuver': <ManeuverIndicator.NotAvailable: 0>, 'spare_1': b'\x00', 'raim': True, 'radio': 34958}
{
    "msg_type": 1,
    "repeat": 0,
    "mmsi": 367380120,
    "status": 0,
    "turn": -128.0,
    "speed": 0.1,
    "accuracy": false,
    "lon": -122.404333,
    "lat": 37.806948,
    "course": 245.2,
    "heading": 511,
    "second": 59,
    "maneuver": 0,
    "spare_1": "AA==",
    "raim": true,
    "radio": 34958
}
MessageType1(msg_type=1, repeat=0, mmsi=227006760, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=False, lon=0.13138, lat=49.475577, course=36.7, heading=511, second=14, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=22136)
MessageType1(msg_type=1, repeat=0, mmsi=205448890, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=4.419442, lat=51.237658, course=63.3, heading=511, second=15, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=2248)
MessageType1(msg_type=1, repeat=0, mmsi=786434, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=1.6, accuracy=True, lon=5.320033, lat=51.967037, course=112.0, heading=511, second=15, maneuver=<ManeuverIndicator.NoSpecialManeuver: 1>, spare_1=b'\x00', raim=False, radio=153208)
MessageType1(msg_type=1, repeat=0, mmsi=249191000, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=23.603633, lat=37.955883, course=247.0, heading=511, second=12, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'@', raim=False, radio=22136)
MessageType1(msg_type=1, repeat=0, mmsi=316013198, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=-130.316237, lat=54.32111, course=237.9, heading=511, second=16, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=81935)
MessageType1(msg_type=1, repeat=0, mmsi=366913120, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=0.0, accuracy=False, lon=-64.620662, lat=18.321188, course=329.5, heading=299, second=16, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=98890)
The raw radio value is: 393222
Communication state is SOTMDA: True
Communication state is ITDMA: False
Communication state: {
    "received_stations": null,
    "slot_number": null,
    "utc_hour": null,
    "utc_minute": null,
    "slot_offset": 6,
    "slot_timeout": 0,
    "sync_state": 3,
    "keep_flag": null,
    "slot_increment": null,
    "num_slots": null
}
MessageType1(msg_type=1, repeat=0, mmsi=227006760, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=False, lon=0.13138, lat=49.475577, course=36.7, heading=511, second=14, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=22136)
MessageType1(msg_type=1, repeat=0, mmsi=205448890, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=4.419442, lat=51.237658, course=63.3, heading=511, second=15, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=2248)
MessageType1(msg_type=1, repeat=0, mmsi=786434, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=1.6, accuracy=True, lon=5.320033, lat=51.967037, course=112.0, heading=511, second=15, maneuver=<ManeuverIndicator.NoSpecialManeuver: 1>, spare_1=b'\x00', raim=False, radio=153208)
MessageType1(msg_type=1, repeat=0, mmsi=249191000, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=23.603633, lat=37.955883, course=247.0, heading=511, second=12, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'@', raim=False, radio=22136)
MessageType1(msg_type=1, repeat=0, mmsi=316013198, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=-130.316237, lat=54.32111, course=237.9, heading=511, second=16, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=81935)
MessageType1(msg_type=1, repeat=0, mmsi=366913120, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=0.0, accuracy=False, lon=-64.620662, lat=18.321188, course=329.5, heading=299, second=16, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=98890)
MessageType1(msg_type=1, repeat=0, mmsi=367078248, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=34.6, accuracy=True, lon=-73.88158, lat=28.230963, course=181.2, heading=192, second=4, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\xa0', raim=False, radio=96)
(Attribute(name='msg_type', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 6, 'd_type': <class 'int'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 1, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='msg_type'), Attribute(name='repeat', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 2, 'd_type': <class 'int'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='repeat'), Attribute(name='mmsi', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 30, 'd_type': <class 'int'>, 'from_converter': <function from_mmsi at 0x7b543062c1f0>, 'to_converter': None, 'signed': False, 'default': None, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='mmsi'), Attribute(name='status', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 4, 'd_type': <class 'int'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=<bound method NavigationStatus.from_value of <enum 'NavigationStatus'>>, kw_only=False, inherited=False, on_setattr=None, alias='status'), Attribute(name='turn', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 8, 'd_type': <class 'float'>, 'from_converter': <function from_turn at 0x7b543062c310>, 'to_converter': <function to_turn at 0x7b543062c280>, 'signed': True, 'default': <TurnRate.NO_TI_DEFAULT: -128.0>, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='turn'), Attribute(name='speed', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 10, 'd_type': <class 'float'>, 'from_converter': <function from_speed at 0x7b543061f940>, 'to_converter': <function to_speed at 0x7b5430628dc0>, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='speed'), Attribute(name='accuracy', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 1, 'd_type': <class 'bool'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='accuracy'), Attribute(name='lon', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 28, 'd_type': <class 'float'>, 'from_converter': <function from_lat_lon at 0x7b5430628e50>, 'to_converter': <function to_lat_lon at 0x7b5430628ee0>, 'signed': True, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='lon'), Attribute(name='lat', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 27, 'd_type': <class 'float'>, 'from_converter': <function from_lat_lon at 0x7b5430628e50>, 'to_converter': <function to_lat_lon at 0x7b5430628ee0>, 'signed': True, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='lat'), Attribute(name='course', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 12, 'd_type': <class 'float'>, 'from_converter': <function from_10th at 0x7b543062c0d0>, 'to_converter': <function to_10th at 0x7b543062c160>, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='course'), Attribute(name='heading', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 9, 'd_type': <class 'int'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='heading'), Attribute(name='second', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 6, 'd_type': <class 'int'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='second'), Attribute(name='maneuver', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 2, 'd_type': <class 'int'>, 'from_converter': <bound method ManeuverIndicator.from_value of <enum 'ManeuverIndicator'>>, 'to_converter': <bound method ManeuverIndicator.from_value of <enum 'ManeuverIndicator'>>, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='maneuver'), Attribute(name='spare_1', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 3, 'd_type': <class 'bytes'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': b'', 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='spare_1'), Attribute(name='raim', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 1, 'd_type': <class 'bool'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='raim'), Attribute(name='radio', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 19, 'd_type': <class 'int'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='radio'))
['!AIVDO,1,1,,A,15M67F@P00G?ur@E`FL8T@000000,0*06']
['!AIVDO,1,1,,B,15M67F@P00G?ur@E`FL8T@000000,0*05']
['!AIVDO,1,1,,A,15M67F@P00G?ur@E`FL8T@000000,0*06']
['!AIVDO,1,1,,B,15M67F@P00G?ur@E`FL8T@000000,0*05']
AISSentence<!AIVDM,1,1,,B,15N4cJ005Jrek0H@9nDW5608EP,013>
{'raw': b'g:1-2-73874,n:157036,s:r003669945,c:12415440354*A', 'receiver_timestamp': '12415440354', 'source_station': 'r003669945', 'destination_station': None, 'line_count': '157036', 'relative_time': None, 'text': None}
{'raw': b'g:1-2-73874,n:157036,s:r003669945,c:12415440354*A', 'receiver_timestamp': '12415440354', 'source_station': 'r003669945', 'destination_station': None, 'line_count': '157036', 'relative_time': None, 'text': None}
MessageType1(msg_type=1, repeat=0, mmsi=366053209, status=<NavigationStatus.RestrictedManoeuverability: 3>, turn=0.0, speed=0.0, accuracy=False, lon=-122.341618, lat=37.802118, course=219.3, heading=1, second=59, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=2281)
MessageType1(msg_type=1, repeat=0, mmsi=366053209, status=<NavigationStatus.RestrictedManoeuverability: 3>, turn=0.0, speed=0.0, accuracy=False, lon=-122.341618, lat=37.802118, course=219.3, heading=1, second=59, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=2281)
MessageType5(msg_type=5, repeat=0, mmsi=210035000, ais_version=0, imo=9514755, callsign='5BXT2', shipname='NORDIC HAMBURG', ship_type=<ShipType.Cargo_HazardousCategory_A: 71>, to_bow=142, to_stern=10, to_port=11, to_starboard=11, epfd=<EpfdType.GPS: 1>, month=7, day=20, hour=5, minute=0, draught=7.1, destination='CTT-LAYBY', dte=False, spare_1=b'\x00')
MessageType5(msg_type=5, repeat=0, mmsi=210035000, ais_version=0, imo=9514755, callsign='5BXT2', shipname='NORDIC HAMBURG', ship_type=<ShipType.Cargo_HazardousCategory_A: 71>, to_bow=142, to_stern=10, to_port=11, to_starboard=11, epfd=<EpfdType.GPS: 1>, month=7, day=20, hour=5, minute=0, draught=7.1, destination='CTT-LAYBY', dte=False, spare_1=b'\x00')
latest 10 tracks 367078248,366913120,316013198,249191000,786434,205448890,227006760
AISTrack(mmsi=249191000, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, lon=23.603633, lat=37.955883, course=247.0, heading=511, imo=None, callsign=None, shipname=None, ship_type=None, to_bow=None, to_stern=None, to_port=None, to_starboard=None, destination=None, last_updated=1794629690.2860937, name=None, ais_version=None, ais_type=None, status=<NavigationStatus.UnderWayUsingEngine: 0>)
********************************************************************************
Country 338
Online 1
PSS 
Region 2
Timestamp 2008-05-09 00:00:00.010000
MessageType1(msg_type=1, repeat=0, mmsi=367309370, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=11.1, accuracy=False, lon=-122.765622, lat=48.177737, course=138.5, heading=511, second=0, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=49181)
********************************************************************************
MessageType1(msg_type=1, repeat=0, mmsi=413355820, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=119.698612, lat=39.932017, course=342.1, heading=259, second=14, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=33328)
********************************************************************************
Country 338
Online 1
PSS 
Region 2
Timestamp 2009-05-09 00:00:00.010000
MessageType1(msg_type=1, repeat=0, mmsi=445451000, status=<NavigationStatus.UnderWaySailing: 8>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=3.9, accuracy=False, lon=120.365158, lat=35.997872, course=310.5, heading=511, second=13, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=99017)
********************************************************************************
MessageType1(msg_type=1, repeat=0, mmsi=412764890, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=11.1, accuracy=True, lon=122.515033, lat=31.652833, course=4.5, heading=0, second=14, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=33329)
______________________ TestMainApp.test_decode_from_file _______________________
----------------------------- Captured stdout call -----------------------------
MessageType1(msg_type=1, repeat=0, mmsi=227006760, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=False, lon=0.13138, lat=49.475577, course=36.7, heading=511, second=14, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=22136)
MessageType1(msg_type=1, repeat=0, mmsi=205448890, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=4.419442, lat=51.237658, course=63.3, heading=511, second=15, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=2248)
MessageType1(msg_type=1, repeat=0, mmsi=786434, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=1.6, accuracy=True, lon=5.320033, lat=51.967037, course=112.0, heading=511, second=15, maneuver=<ManeuverIndicator.NoSpecialManeuver: 1>, spare_1=b'\x00', raim=False, radio=153208)
MessageType1(msg_type=1, repeat=0, mmsi=249191000, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=23.603633, lat=37.955883, course=247.0, heading=511, second=12, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'@', raim=False, radio=22136)
MessageType1(msg_type=1, repeat=0, mmsi=316013198, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=-130.316237, lat=54.32111, course=237.9, heading=511, second=16, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=81935)
MessageType1(msg_type=1, repeat=0, mmsi=366913120, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=0.0, accuracy=False, lon=-64.620662, lat=18.321188, course=329.5, heading=299, second=16, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=98890)
MessageType5(msg_type=5, repeat=0, mmsi=351759000, ais_version=0, imo=9134270, callsign='3FOF8', shipname='EVER DIADEM', ship_type=<ShipType.Cargo: 70>, to_bow=225, to_stern=70, to_port=1, to_starboard=31, epfd=<EpfdType.GPS: 1>, month=5, day=15, hour=14, minute=0, draught=12.2, destination='NEW YORK', dte=False, spare_1=b'\x00')
________________________ TestMainApp.test_decode_single ________________________
----------------------------- Captured stdout call -----------------------------
MessageType9(msg_type=9, repeat=0, mmsi=111232511, alt=303, speed=42.0, accuracy=False, lon=-6.278843, lat=58.144, course=154.5, second=15, reserved_1=0, dte=True, spare_1=b'\x00', assigned=False, raim=False, radio=33392)
_____________________ TestMainApp.test_decode_single_multi _____________________
----------------------------- Captured stdout call -----------------------------
MessageType5(msg_type=5, repeat=0, mmsi=210035000, ais_version=0, imo=9514755, callsign='5BXT2', shipname='NORDIC HAMBURG', ship_type=<ShipType.Cargo_HazardousCategory_A: 71>, to_bow=142, to_stern=10, to_port=11, to_starboard=11, epfd=<EpfdType.GPS: 1>, month=7, day=20, hour=5, minute=0, draught=7.1, destination='CTT-LAYBY', dte=False, spare_1=b'\x00')
MessageType5(msg_type=5, repeat=0, mmsi=210035000, ais_version=0, imo=9514755, callsign='5BXT2', shipname='NORDIC HAMBURG', ship_type=<ShipType.Cargo_HazardousCategory_A: 71>, to_bow=142, to_stern=10, to_port=11, to_starboard=11, epfd=<EpfdType.GPS: 1>, month=7, day=20, hour=5, minute=0, draught=7.1, destination='CTT-LAYBY', dte=False, spare_1=b'\x00')
___________________________ TestMainApp.test_parser ____________________________
----------------------------- Captured stderr call -----------------------------
usage: ais-decode [-h] [-f [IN_FILE]] [-o OUT_FILE] {socket,single} ...
ais-decode: error: argument -f/--file: can't open 'invalid': [Errno 2] No such file or directory: 'invalid'
usage: ais-decode single [-h] messages [messages ...]
ais-decode single: error: the following arguments are required: messages
usage: ais-decode socket [-h] [-t [{udp,tcp}]] destination port
ais-decode socket: error: argument -t/--type: invalid choice: 'something else' (choose from 'udp', 'tcp')
- generated xml file: /tmp/rebench-grade-380ac56871fa4cb3879e75b2b69e5fbe.xml --
=========================== short test summary info ============================
PASSED tests/test_constants.py::TestConstants::test_maneuver_indication
PASSED tests/test_constants.py::TestConstants::test_nav_status
PASSED tests/test_constants.py::TestConstants::test_navaid
PASSED tests/test_constants.py::TestConstants::test_ship_types
PASSED tests/test_constants.py::TestConstants::test_station_intervals
PASSED tests/test_constants.py::TestConstants::test_station_types
PASSED tests/test_constants.py::TestConstants::test_t_mode
PASSED tests/test_constants.py::TestConstants::test_that_nav_data_values_are_kept_for_all_values
PASSED tests/test_decode.py::TestAIS::test_b64encode_str
PASSED tests/test_decode.py::TestAIS::test_b64encode_str_empty
PASSED tests/test_decode.py::TestAIS::test_bits2bytes
PASSED tests/test_decode.py::TestAIS::test_broken_messages
PASSED tests/test_decode.py::TestAIS::test_byte_stream
PASSED tests/test_decode.py::TestAIS::test_bytes2bits
PASSED tests/test_decode.py::TestAIS::test_common_invalid_inputs_to_the_decode_function
PASSED tests/test_decode.py::TestAIS::test_decode_1_speed
PASSED tests/test_decode.py::TestAIS::test_decode_and_merge
PASSED tests/test_decode.py::TestAIS::test_decode_does_not_raise_an_error_by_default
PASSED tests/test_decode.py::TestAIS::test_decode_does_not_raise_an_error_if_error_if_checksum_invalid_is_false
PASSED tests/test_decode.py::TestAIS::test_decode_does_raise_an_error_if_error_if_checksum_invalid_is_true
PASSED tests/test_decode.py::TestAIS::test_decode_into_bit_array_with_non_printable_characters
PASSED tests/test_decode.py::TestAIS::test_decode_out_of_order
PASSED tests/test_decode.py::TestAIS::test_decode_pos_1_2_3
PASSED tests/test_decode.py::TestAIS::test_empty_channel
PASSED tests/test_decode.py::TestAIS::test_get_comm_state_type_18_itdma_base_indirect
PASSED tests/test_decode.py::TestAIS::test_get_comm_state_type_18_sotdma_base_inidrect
PASSED tests/test_decode.py::TestAIS::test_get_comm_state_type_18_sotdma_utc_direct
PASSED tests/test_decode.py::TestAIS::test_get_sotdma_comm_state_utc_direct
PASSED tests/test_decode.py::TestAIS::test_get_sotdma_comm_state_utc_direct_slot_number
PASSED tests/test_decode.py::TestAIS::test_get_sotdma_comm_state_utc_direct_slot_timeout
PASSED tests/test_decode.py::TestAIS::test_gh_ais_message_decode
PASSED tests/test_decode.py::TestAIS::test_invalid_timestamp_message
PASSED tests/test_decode.py::TestAIS::test_is_sotdma_or_itdma
PASSED tests/test_decode.py::TestAIS::test_issue_46_a
PASSED tests/test_decode.py::TestAIS::test_issue_46_b
PASSED tests/test_decode.py::TestAIS::test_issue_50
PASSED tests/test_decode.py::TestAIS::test_issue_88
PASSED tests/test_decode.py::TestAIS::test_messages_with_proprietary_suffix
PASSED tests/test_decode.py::TestAIS::test_msg_too_short_enum_is_none
PASSED tests/test_decode.py::TestAIS::test_msg_type
PASSED tests/test_decode.py::TestAIS::test_msg_type_10_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_10_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_11
PASSED tests/test_decode.py::TestAIS::test_msg_type_12_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_12_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_13
PASSED tests/test_decode.py::TestAIS::test_msg_type_14
PASSED tests/test_decode.py::TestAIS::test_msg_type_15_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_15_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_16
PASSED tests/test_decode.py::TestAIS::test_msg_type_17_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_17_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_17_to_json
PASSED tests/test_decode.py::TestAIS::test_msg_type_18
PASSED tests/test_decode.py::TestAIS::test_msg_type_18_speed
PASSED tests/test_decode.py::TestAIS::test_msg_type_19
PASSED tests/test_decode.py::TestAIS::test_msg_type_1_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_1_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_1_c
PASSED tests/test_decode.py::TestAIS::test_msg_type_20
PASSED tests/test_decode.py::TestAIS::test_msg_type_21
PASSED tests/test_decode.py::TestAIS::test_msg_type_22_addressed
PASSED tests/test_decode.py::TestAIS::test_msg_type_22_broadcast
PASSED tests/test_decode.py::TestAIS::test_msg_type_23
PASSED tests/test_decode.py::TestAIS::test_msg_type_24
PASSED tests/test_decode.py::TestAIS::test_msg_type_24_with_160_bits
PASSED tests/test_decode.py::TestAIS::test_msg_type_24_with_168_bits
PASSED tests/test_decode.py::TestAIS::test_msg_type_25_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_25_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_25_c
PASSED tests/test_decode.py::TestAIS::test_msg_type_25_to_json
PASSED tests/test_decode.py::TestAIS::test_msg_type_26_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_26_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_26_to_json
PASSED tests/test_decode.py::TestAIS::test_msg_type_27
PASSED tests/test_decode.py::TestAIS::test_msg_type_27_signed
PASSED tests/test_decode.py::TestAIS::test_msg_type_3
PASSED tests/test_decode.py::TestAIS::test_msg_type_4_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_4_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_5
PASSED tests/test_decode.py::TestAIS::test_msg_type_6
PASSED tests/test_decode.py::TestAIS::test_msg_type_6_json_reverse
PASSED tests/test_decode.py::TestAIS::test_msg_type_6_to_json
PASSED tests/test_decode.py::TestAIS::test_msg_type_7
PASSED tests/test_decode.py::TestAIS::test_msg_type_8
PASSED tests/test_decode.py::TestAIS::test_msg_type_8_multipart
PASSED tests/test_decode.py::TestAIS::test_msg_type_8_to_json
PASSED tests/test_decode.py::TestAIS::test_msg_type_9
PASSED tests/test_decode.py::TestAIS::test_msg_with_more_that_82_chars_payload
PASSED tests/test_decode.py::TestAIS::test_multiline_message
PASSED tests/test_decode.py::TestAIS::test_nmea_decode
PASSED tests/test_decode.py::TestAIS::test_nmea_decode_unknown_msg
PASSED tests/test_decode.py::TestAIS::test_none_value_converter_for_creation
PASSED tests/test_decode.py::TestAIS::test_none_value_converter_for_decoding
PASSED tests/test_decode.py::TestAIS::test_none_values_converter_for_all_messages
PASSED tests/test_decode.py::TestAIS::test_rot_decode_yields_expected_values
PASSED tests/test_decode.py::TestAIS::test_rot_encode_decode
PASSED tests/test_decode.py::TestAIS::test_rot_encode_yields_expected_values
PASSED tests/test_decode.py::TestAIS::test_sotdma_time_conversion
PASSED tests/test_decode.py::TestAIS::test_special_position_report
PASSED tests/test_decode.py::TestAIS::test_static_data_report
PASSED tests/test_decode.py::TestAIS::test_that_decode_nmea_and_ais_works_with_proprietary_messages
PASSED tests/test_decode.py::TestAIS::test_that_lat_and_long_are_rounded_correctly
PASSED tests/test_decode.py::TestAIS::test_that_the_payload_does_not_change_when_encoding_decoding
PASSED tests/test_decode.py::TestAIS::test_timestamp_message
PASSED tests/test_decode.py::TestAIS::test_to_dict_non_enum
PASSED tests/test_decode.py::TestAIS::test_to_json
PASSED tests/test_decode.py::TestAIS::test_type_22_very_short
PASSED tests/test_decode.py::TestAIS::test_type_25_very_short
PASSED tests/test_decode.py::TestAIS::test_type_26_very_short
PASSED tests/test_decode.py::TestAIS::test_types_for_messages
PASSED tests/test_decode_raw.py::TestDecode::test_bytes_invalid
PASSED tests/test_decode_raw.py::TestDecode::test_bytes_valid
PASSED tests/test_decode_raw.py::TestDecode::test_decode_multiline_message
PASSED tests/test_decode_raw.py::TestDecode::test_decode_total_garbage
PASSED tests/test_decode_raw.py::TestDecode::test_multipart_error_message
PASSED tests/test_decode_raw.py::TestDecode::test_str_invalid
PASSED tests/test_decode_raw.py::TestDecode::test_str_valid
PASSED tests/test_decode_raw.py::TestDecode::test_too_many_messages
PASSED tests/test_encode.py::test_widths
PASSED tests/test_encode.py::test_variable_message_length_width
PASSED tests/test_encode.py::test_encode_msg_table
PASSED tests/test_encode.py::test_invalid_talker_id
PASSED tests/test_encode.py::test_encode_payload_invalid_talker_id
PASSED tests/test_encode.py::test_invalid_radio_channel
PASSED tests/test_encode.py::test_encode_payload_error_radio
PASSED tests/test_encode.py::test_data_to_payload
PASSED tests/test_encode.py::test_get_ais_type
PASSED tests/test_encode.py::test_str_to_bin
PASSED tests/test_encode.py::test_int_to_bin
PASSED tests/test_encode.py::test_decode_encode
PASSED tests/test_encode.py::test_encode_type_27
PASSED tests/test_encode.py::test_encode_type_26
PASSED tests/test_encode.py::test_encode_type_25_b
PASSED tests/test_encode.py::test_encode_type_25_a
PASSED tests/test_encode.py::test_encode_type_24_partno_invalid
PASSED tests/test_encode.py::test_encode_type_24_a
PASSED tests/test_encode.py::test_encode_type_24_b
PASSED tests/test_encode.py::test_encode_type_23
PASSED tests/test_encode.py::test_encode_type_22_b
PASSED tests/test_encode.py::test_encode_type_22_a
PASSED tests/test_encode.py::test_encode_type_21
PASSED tests/test_encode.py::test_encode_type_20
PASSED tests/test_encode.py::test_encode_type_19
PASSED tests/test_encode.py::test_encode_type_18_with_speed_and_course
PASSED tests/test_encode.py::test_encode_type_18
PASSED tests/test_encode.py::test_encode_type_17_b
PASSED tests/test_encode.py::test_encode_type_17_a
PASSED tests/test_encode.py::test_encode_type_16
PASSED tests/test_encode.py::test_encode_type_15_a
PASSED tests/test_encode.py::test_encode_type_15
PASSED tests/test_encode.py::test_encode_type_14
PASSED tests/test_encode.py::test_encode_type_13
PASSED tests/test_encode.py::test_encode_type_12_with_some_text
PASSED tests/test_encode.py::test_encode_type_12_with_no_text
PASSED tests/test_encode.py::test_encode_type_12_with_empty_text
PASSED tests/test_encode.py::test_encode_type_12_with_max_length_text
PASSED tests/test_encode.py::test_encode_type_12_with_longer_than_max_length_length_text
PASSED tests/test_encode.py::test_encode_type_11
PASSED tests/test_encode.py::test_encode_type_10
PASSED tests/test_encode.py::test_encode_type_9
PASSED tests/test_encode.py::test_encode_type_8
PASSED tests/test_encode.py::test_encode_type_7
PASSED tests/test_encode.py::test_encode_type_6_bytes
PASSED tests/test_encode.py::test_encode_type_6
PASSED tests/test_encode.py::test_encode_type_4
PASSED tests/test_encode.py::test_encode_type_5_issue_59
PASSED tests/test_encode.py::test_encode_type_5
PASSED tests/test_encode.py::test_encode_type_5_default
PASSED tests/test_encode.py::test_encode_msg_type2
PASSED tests/test_encode.py::test_encode_msg_type_3
PASSED tests/test_encode.py::test_encode_type_1_default
PASSED tests/test_encode.py::test_encode_type_1
PASSED tests/test_encode.py::test_mmsi_too_long
PASSED tests/test_encode.py::test_lon_too_large
PASSED tests/test_encode.py::test_ship_name_too_lon
PASSED tests/test_encode.py::test_int_to_bytes
PASSED tests/test_encode.py::test_to_six_bit
PASSED tests/test_encode.py::test_encode_ascii_6_bit
PASSED tests/test_encode.py::test_encode_does_not_exceed_nmea_sentence_length_limit
PASSED tests/test_examples.py::TestExamples::test_run_every_file
PASSED tests/test_file_stream.py::TestFileReaderStream::test_invalid_filename
PASSED tests/test_file_stream.py::TestFileReaderStream::test_marine_traffic_sample
PASSED tests/test_file_stream.py::TestFileReaderStream::test_mixed_content
PASSED tests/test_file_stream.py::TestFileReaderStream::test_nmea_sort_index_error
PASSED tests/test_file_stream.py::TestFileReaderStream::test_nmea_sort_invalid_frag_cnt
PASSED tests/test_file_stream.py::TestFileReaderStream::test_nmea_sorter_sorted
PASSED tests/test_file_stream.py::TestFileReaderStream::test_nmea_sorter_unsorted
PASSED tests/test_file_stream.py::TestFileReaderStream::test_reader
PASSED tests/test_file_stream.py::TestFileReaderStream::test_reader_with_open
PASSED tests/test_file_stream.py::TestFileReaderStream::test_sort_with_different_msg_ids
PASSED tests/test_file_stream.py::TestFileReaderStream::test_timestamp_messages
PASSED tests/test_filters.py::TestNoneFilter::test_filtering_missing_attributes
PASSED tests/test_filters.py::TestNoneFilter::test_filtering_none_attributes
PASSED tests/test_filters.py::TestMessageTypeFilter::test_message_type_filtering
PASSED tests/test_filters.py::TestDistanceFilter::test_all_within_distance
PASSED tests/test_filters.py::TestDistanceFilter::test_distance_filtering
PASSED tests/test_filters.py::TestDistanceFilter::test_none_within_distance
PASSED tests/test_filters.py::TestDistanceFilter::test_specific_coordinate_distance
PASSED tests/test_filters.py::TestGridFilter::test_central_park_grid
PASSED tests/test_filters.py::TestGridFilter::test_grid_filtering
PASSED tests/test_filters.py::TestFilterChain::test_complex_filter_chain
PASSED tests/test_filters.py::TestFilterChain::test_filter_chain
PASSED tests/test_filters.py::TestAttributeFilter::test_attribute_filtering
PASSED tests/test_filters.py::TestHaversine::test_london_to_paris
PASSED tests/test_filters.py::TestHaversine::test_nyc_to_la
PASSED tests/test_filters.py::TestHaversine::test_sydney_to_melbourne
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_get_three_digits
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_1
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_2
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_3
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_4
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_5
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_6
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_7
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_8
PASSED tests/test_generic_stream.py::TestGenericStream::test_empty_stream
PASSED tests/test_generic_stream.py::TestGenericStream::test_garbage_stream
PASSED tests/test_generic_stream.py::TestGenericStream::test_invalid_msg
PASSED tests/test_generic_stream.py::TestIterMessages::test_init_from_bytes_transforms_bytes_to_list
PASSED tests/test_generic_stream.py::TestIterMessages::test_init_from_bytes_with_multiple_messages
PASSED tests/test_generic_stream.py::TestIterMessages::test_init_from_ignores_error_if_enabled
PASSED tests/test_generic_stream.py::TestIterMessages::test_init_from_str
PASSED tests/test_generic_stream.py::TestIterMessages::test_init_from_str_throws_error_if_invalid
PASSED tests/test_generic_stream.py::TestIterMessages::test_iter_is_generator
PASSED tests/test_generic_stream.py::TestIterMessages::test_iter_messages_does_assemble_multiline_messages
PASSED tests/test_generic_stream.py::TestIterMessages::test_iter_messages_handles_single_message
PASSED tests/test_main.py::TestMainApp::test_decode_from_file
PASSED tests/test_main.py::TestMainApp::test_decode_single
PASSED tests/test_main.py::TestMainApp::test_decode_single_multi
PASSED tests/test_main.py::TestMainApp::test_parser
PASSED tests/test_nmea.py::TestNMEA::test_attrs
PASSED tests/test_nmea.py::TestNMEA::test_chk_to_int_with_fill_bits
PASSED tests/test_nmea.py::TestNMEA::test_chk_to_int_with_missing_checksum
PASSED tests/test_nmea.py::TestNMEA::test_chk_to_int_with_missing_fill_bits
PASSED tests/test_nmea.py::TestNMEA::test_chk_to_int_with_valid_checksum
PASSED tests/test_nmea.py::TestNMEA::test_dict
PASSED tests/test_nmea.py::TestNMEA::test_from_bytes
PASSED tests/test_nmea.py::TestNMEA::test_from_str
PASSED tests/test_nmea.py::TestNMEA::test_get_item
PASSED tests/test_nmea.py::TestNMEA::test_get_item_raises_key_error
PASSED tests/test_nmea.py::TestNMEA::test_get_item_raises_type_error
PASSED tests/test_nmea.py::TestNMEA::test_message_assembling
PASSED tests/test_nmea.py::TestNMEA::test_message_eq_method
PASSED tests/test_nmea.py::TestNMEA::test_missing_checksum
PASSED tests/test_nmea.py::TestNMEA::test_single
PASSED tests/test_nmea.py::TestNMEA::test_talker
PASSED tests/test_nmea.py::TestNMEA::test_that_a_valid_checksum_is_correctly_identified
PASSED tests/test_nmea.py::TestNMEA::test_that_a_valid_checksum_is_correctly_identified_for_multi_part_msgs
PASSED tests/test_nmea.py::TestNMEA::test_that_an_invalid_checksum_is_correctly_identified
PASSED tests/test_nmea.py::TestNMEA::test_that_an_invalid_checksum_is_correctly_identified_for_multi_part_msgs
PASSED tests/test_nmea.py::TestNMEA::test_type
PASSED tests/test_nmea.py::TestNMEA::test_validity
PASSED tests/test_nmea.py::TestNMEA::test_values
PASSED tests/test_nmea.py::TestNMEA::test_wrong_type
PASSED tests/test_socket.py::SocketStreamingTestCase::test_that_a_single_line_is_returned_as_is
PASSED tests/test_socket.py::SocketStreamingTestCase::test_that_all_lines_are_returned
PASSED tests/test_socket.py::SocketStreamingTestCase::test_that_all_lines_are_returned_for_incomplete_lines
PASSED tests/test_socket.py::SocketStreamingTestCase::test_that_all_lines_are_returned_when_using_crlf
PASSED tests/test_socket.py::SocketStreamingTestCase::test_that_many_lines_are_returned
PASSED tests/test_socket.py::SocketStreamingTestCase::test_that_no_partial_artifacts_are_added_after_a_complete_line
PASSED tests/test_tag_block.py::TagBlockTestCase::test_multiple_messages
PASSED tests/test_tag_block.py::TagBlockTestCase::test_spire_maritime_format
PASSED tests/test_tag_block.py::TagBlockTestCase::test_tag_block_with_line_count
PASSED tests/test_tag_block.py::TagBlockTestCase::test_tag_block_with_multiple_unknown_fields
PASSED tests/test_tag_block.py::TagBlockTestCase::test_that_a_tag_block_is_lazily_evaluated
PASSED tests/test_tag_block.py::TagBlockTestCase::test_that_the_factory_is_gentle_with_malformed_tag_blocks
PASSED tests/test_tag_block.py::TagBlockTestCase::test_that_the_factory_pre_processes_correctly
PASSED tests/test_tag_block.py::TagBlockTestCase::test_that_the_factory_removes_the_leading_tag_block
PASSED tests/test_tag_block.py::TagBlockTestCase::test_that_unknown_tag_blocks_are_ignored
PASSED tests/test_tag_block.py::TagBlockTestCase::test_that_unknown_tag_blocks_can_exported_as_dicts
PASSED tests/test_talker_ids.py::TestTalkerIds::test_ab
PASSED tests/test_talker_ids.py::TestTalkerIds::test_ad
PASSED tests/test_talker_ids.py::TestTalkerIds::test_ai
PASSED tests/test_talker_ids.py::TestTalkerIds::test_an
PASSED tests/test_talker_ids.py::TestTalkerIds::test_ar
PASSED tests/test_talker_ids.py::TestTalkerIds::test_as
PASSED tests/test_talker_ids.py::TestTalkerIds::test_at
PASSED tests/test_talker_ids.py::TestTalkerIds::test_ax
PASSED tests/test_talker_ids.py::TestTalkerIds::test_bs
PASSED tests/test_talker_ids.py::TestTalkerIds::test_sa
PASSED tests/test_tcp_stream.py::TestTCPStream::test_default_buf_size
PASSED tests/test_tcp_stream.py::TestTCPStream::test_invalid_endpoint
PASSED tests/test_tracker.py::TrackerTestCase::test_that_clean_up_deletes_tracks_if_they_expire
PASSED tests/test_tracker.py::TrackerTestCase::test_that_clean_up_does_nothing_if_ttl_is_none
PASSED tests/test_tracker.py::TrackerTestCase::test_that_get_track_returns_track_if_it_exists
PASSED tests/test_tracker.py::TrackerTestCase::test_that_insert_or_update_sets_oldest_timestamp
PASSED tests/test_tracker.py::TrackerTestCase::test_that_n_latest_returns_empty_list_for_empty
PASSED tests/test_tracker.py::TrackerTestCase::test_that_n_latest_returns_tracks_in_correct_order
PASSED tests/test_tracker.py::TrackerTestCase::test_that_ordered_tracker_handles_cleanup
PASSED tests/test_tracker.py::TrackerTestCase::test_that_ordered_tracker_raises_value_error_if_older_record_is_received
PASSED tests/test_tracker.py::TrackerTestCase::test_that_ordered_tracker_returns_correct_latest_n
PASSED tests/test_tracker.py::TrackerTestCase::test_that_pop_tracks_returns_and_deletes_track_if_it_exists
PASSED tests/test_tracker.py::TrackerTestCase::test_that_poplast_is_non_destructive
PASSED tests/test_tracker.py::TrackerTestCase::test_that_tracker_works_as_context_manager
PASSED tests/test_tracker.py::TrackerTestCase::test_that_tracks_property_returns_tracks_as_list
PASSED tests/test_tracker.py::TrackerTestCase::test_that_update_always_updates_all_fields
PASSED tests/test_tracker.py::TrackerTestCase::test_that_update_cleans_up_afterwards
PASSED tests/test_tracker.py::TrackerTestCase::test_that_update_raises_error_is_timestamp_is_younger
PASSED tests/test_udp_stream.py::TestOutOfOrder::test_stream
SKIPPED [1] tests/test_file_stream.py:154: Takes too long
SKIPPED [1] tests/test_tcp_stream.py:55: Skip for now, because there is a Threading problem
======================== 300 passed, 2 skipped in 1.18s ========================
Test errors Included
Test report (JUnit) Included
<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite name="pytest" errors="0" failures="0" skipped="2" tests="302" time="1.165" timestamp="2026-11-14T04:14:49.396548+00:00" hostname="4675bf3b4988"><testcase classname="tests.test_constants.TestConstants" name="test_maneuver_indication" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_nav_status" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_navaid" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_ship_types" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_station_intervals" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_station_types" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_t_mode" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_that_nav_data_values_are_kept_for_all_values" time="0.001" /><testcase classname="tests.test_decode.TestAIS" name="test_b64encode_str" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_b64encode_str_empty" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_bits2bytes" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_broken_messages" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_byte_stream" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_bytes2bits" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_common_invalid_inputs_to_the_decode_function" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_1_speed" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_and_merge" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_does_not_raise_an_error_by_default" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_does_not_raise_an_error_if_error_if_checksum_invalid_is_false" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_does_raise_an_error_if_error_if_checksum_invalid_is_true" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_into_bit_array_with_non_printable_characters" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_out_of_order" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_pos_1_2_3" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_empty_channel" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_get_comm_state_type_18_itdma_base_indirect" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_get_comm_state_type_18_sotdma_base_inidrect" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_get_comm_state_type_18_sotdma_utc_direct" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_get_sotdma_comm_state_utc_direct" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_get_sotdma_comm_state_utc_direct_slot_number" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_get_sotdma_comm_state_utc_direct_slot_timeout" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_gh_ais_message_decode" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_invalid_timestamp_message" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_is_sotdma_or_itdma" time="0.001" /><testcase classname="tests.test_decode.TestAIS" name="test_issue_46_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_issue_46_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_issue_50" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_issue_88" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_messages_with_proprietary_suffix" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_too_short_enum_is_none" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_10_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_10_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_11" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_12_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_12_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_13" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_14" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_15_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_15_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_16" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_17_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_17_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_17_to_json" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_18" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_18_speed" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_19" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_1_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_1_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_1_c" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_20" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_21" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_22_addressed" time="0.002" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_22_broadcast" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_23" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_24" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_24_with_160_bits" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_24_with_168_bits" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_25_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_25_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_25_c" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_25_to_json" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_26_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_26_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_26_to_json" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_27" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_27_signed" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_3" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_4_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_4_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_5" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_6" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_6_json_reverse" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_6_to_json" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_7" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_8" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_8_multipart" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_8_to_json" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_9" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_with_more_that_82_chars_payload" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_multiline_message" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_nmea_decode" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_nmea_decode_unknown_msg" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_none_value_converter_for_creation" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_none_value_converter_for_decoding" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_none_values_converter_for_all_messages" time="0.002" /><testcase classname="tests.test_decode.TestAIS" name="test_rot_decode_yields_expected_values" time="0.001" /><testcase classname="tests.test_decode.TestAIS" name="test_rot_encode_decode" time="0.001" /><testcase classname="tests.test_decode.TestAIS" name="test_rot_encode_yields_expected_values" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_sotdma_time_conversion" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_special_position_report" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_static_data_report" time="0.001" /><testcase classname="tests.test_decode.TestAIS" name="test_that_decode_nmea_and_ais_works_with_proprietary_messages" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_that_lat_and_long_are_rounded_correctly" time="0.001" /><testcase classname="tests.test_decode.TestAIS" name="test_that_the_payload_does_not_change_when_encoding_decoding" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_timestamp_message" time="0.001" /><testcase classname="tests.test_decode.TestAIS" name="test_to_dict_non_enum" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_to_json" time="0.001" /><testcase classname="tests.test_decode.TestAIS" name="test_type_22_very_short" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_type_25_very_short" time="0.001" /><testcase classname="tests.test_decode.TestAIS" name="test_type_26_very_short" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_types_for_messages" time="0.000" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_bytes_invalid" time="0.000" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_bytes_valid" time="0.000" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_decode_multiline_message" time="0.001" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_decode_total_garbage" time="0.001" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_multipart_error_message" time="0.001" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_str_invalid" time="0.000" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_str_valid" time="0.001" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_too_many_messages" time="0.000" /><testcase classname="tests.test_encode" name="test_widths" time="0.000" /><testcase classname="tests.test_encode" name="test_variable_message_length_width" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_msg_table" time="0.000" /><testcase classname="tests.test_encode" name="test_invalid_talker_id" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_payload_invalid_talker_id" time="0.000" /><testcase classname="tests.test_encode" name="test_invalid_radio_channel" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_payload_error_radio" time="0.000" /><testcase classname="tests.test_encode" name="test_data_to_payload" time="0.000" /><testcase classname="tests.test_encode" name="test_get_ais_type" time="0.000" /><testcase classname="tests.test_encode" name="test_str_to_bin" time="0.000" /><testcase classname="tests.test_encode" name="test_int_to_bin" time="0.000" /><testcase classname="tests.test_encode" name="test_decode_encode" time="0.002" /><testcase classname="tests.test_encode" name="test_encode_type_27" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_26" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_25_b" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_25_a" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_24_partno_invalid" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_24_a" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_24_b" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_23" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_22_b" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_22_a" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_21" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_20" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_19" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_18_with_speed_and_course" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_18" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_17_b" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_17_a" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_16" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_15_a" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_15" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_14" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_13" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_12_with_some_text" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_12_with_no_text" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_12_with_empty_text" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_12_with_max_length_text" time="0.001" /><testcase classname="tests.test_encode" name="test_encode_type_12_with_longer_than_max_length_length_text" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_11" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_10" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_9" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_8" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_7" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_6_bytes" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_6" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_4" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_5_issue_59" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_5" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_5_default" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_msg_type2" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_msg_type_3" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_1_default" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_1" time="0.000" /><testcase classname="tests.test_encode" name="test_mmsi_too_long" time="0.000" /><testcase classname="tests.test_encode" name="test_lon_too_large" time="0.000" /><testcase classname="tests.test_encode" name="test_ship_name_too_lon" time="0.000" /><testcase classname="tests.test_encode" name="test_int_to_bytes" time="0.000" /><testcase classname="tests.test_encode" name="test_to_six_bit" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_ascii_6_bit" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_does_not_exceed_nmea_sentence_length_limit" time="0.000" /><testcase classname="tests.test_examples.TestExamples" name="test_run_every_file" time="0.672" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_invalid_filename" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_large_file" time="0.000"><skipped type="pytest.skip" message="Takes too long">/testbed/tests/test_file_stream.py:154: Takes too long</skipped></testcase><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_marine_traffic_sample" time="0.020" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_mixed_content" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_nmea_sort_index_error" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_nmea_sort_invalid_frag_cnt" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_nmea_sorter_sorted" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_nmea_sorter_unsorted" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_reader" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_reader_with_open" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_sort_with_different_msg_ids" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_timestamp_messages" time="0.000" /><testcase classname="tests.test_filters.TestNoneFilter" name="test_filtering_missing_attributes" time="0.000" /><testcase classname="tests.test_filters.TestNoneFilter" name="test_filtering_none_attributes" time="0.000" /><testcase classname="tests.test_filters.TestMessageTypeFilter" name="test_message_type_filtering" time="0.000" /><testcase classname="tests.test_filters.TestDistanceFilter" name="test_all_within_distance" time="0.000" /><testcase classname="tests.test_filters.TestDistanceFilter" name="test_distance_filtering" time="0.000" /><testcase classname="tests.test_filters.TestDistanceFilter" name="test_none_within_distance" time="0.000" /><testcase classname="tests.test_filters.TestDistanceFilter" name="test_specific_coordinate_distance" time="0.000" /><testcase classname="tests.test_filters.TestGridFilter" name="test_central_park_grid" time="0.000" /><testcase classname="tests.test_filters.TestGridFilter" name="test_grid_filtering" time="0.000" /><testcase classname="tests.test_filters.TestFilterChain" name="test_complex_filter_chain" time="0.000" /><testcase classname="tests.test_filters.TestFilterChain" name="test_filter_chain" time="0.000" /><testcase classname="tests.test_filters.TestAttributeFilter" name="test_attribute_filtering" time="0.000" /><testcase classname="tests.test_filters.TestHaversine" name="test_london_to_paris" time="0.000" /><testcase classname="tests.test_filters.TestHaversine" name="test_nyc_to_la" time="0.000" /><testcase classname="tests.test_filters.TestHaversine" name="test_sydney_to_melbourne" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_get_three_digits" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_1" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_2" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_3" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_4" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_5" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_6" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_7" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_8" time="0.000" /><testcase classname="tests.test_generic_stream.TestGenericStream" name="test_empty_stream" time="0.000" /><testcase classname="tests.test_generic_stream.TestGenericStream" name="test_garbage_stream" time="0.000" /><testcase classname="tests.test_generic_stream.TestGenericStream" name="test_invalid_msg" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_init_from_bytes_transforms_bytes_to_list" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_init_from_bytes_with_multiple_messages" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_init_from_ignores_error_if_enabled" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_init_from_str" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_init_from_str_throws_error_if_invalid" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_iter_is_generator" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_iter_messages_does_assemble_multiline_messages" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_iter_messages_handles_single_message" time="0.000" /><testcase classname="tests.test_main.TestMainApp" name="test_decode_from_file" time="0.001" /><testcase classname="tests.test_main.TestMainApp" name="test_decode_single" time="0.000" /><testcase classname="tests.test_main.TestMainApp" name="test_decode_single_multi" time="0.000" /><testcase classname="tests.test_main.TestMainApp" name="test_parser" time="0.002" /><testcase classname="tests.test_nmea.TestNMEA" name="test_attrs" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_chk_to_int_with_fill_bits" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_chk_to_int_with_missing_checksum" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_chk_to_int_with_missing_fill_bits" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_chk_to_int_with_valid_checksum" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_dict" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_from_bytes" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_from_str" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_get_item" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_get_item_raises_key_error" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_get_item_raises_type_error" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_message_assembling" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_message_eq_method" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_missing_checksum" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_single" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_talker" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_that_a_valid_checksum_is_correctly_identified" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_that_a_valid_checksum_is_correctly_identified_for_multi_part_msgs" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_that_an_invalid_checksum_is_correctly_identified" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_that_an_invalid_checksum_is_correctly_identified_for_multi_part_msgs" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_type" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_validity" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_values" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_wrong_type" time="0.000" /><testcase classname="tests.test_socket.SocketStreamingTestCase" name="test_that_a_single_line_is_returned_as_is" time="0.000" /><testcase classname="tests.test_socket.SocketStreamingTestCase" name="test_that_all_lines_are_returned" time="0.000" /><testcase classname="tests.test_socket.SocketStreamingTestCase" name="test_that_all_lines_are_returned_for_incomplete_lines" time="0.000" /><testcase classname="tests.test_socket.SocketStreamingTestCase" name="test_that_all_lines_are_returned_when_using_crlf" time="0.000" /><testcase classname="tests.test_socket.SocketStreamingTestCase" name="test_that_many_lines_are_returned" time="0.000" /><testcase classname="tests.test_socket.SocketStreamingTestCase" name="test_that_no_partial_artifacts_are_added_after_a_complete_line" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_multiple_messages" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_spire_maritime_format" time="0.001" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_tag_block_with_line_count" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_tag_block_with_multiple_unknown_fields" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_that_a_tag_block_is_lazily_evaluated" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_that_the_factory_is_gentle_with_malformed_tag_blocks" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_that_the_factory_pre_processes_correctly" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_that_the_factory_removes_the_leading_tag_block" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_that_unknown_tag_blocks_are_ignored" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_that_unknown_tag_blocks_can_exported_as_dicts" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_ab" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_ad" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_ai" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_an" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_ar" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_as" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_at" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_ax" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_bs" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_sa" time="0.000" /><testcase classname="tests.test_tcp_stream.TestTCPStream" name="test_default_buf_size" time="0.000" /><testcase classname="tests.test_tcp_stream.TestTCPStream" name="test_invalid_endpoint" time="0.000" /><testcase classname="tests.test_tcp_stream.TestTCPStream" name="test_tcp_stream" time="0.000"><skipped type="pytest.skip" message="Skip for now, because there is a Threading problem">/testbed/tests/test_tcp_stream.py:55: Skip for now, because there is a Threading problem</skipped></testcase><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_clean_up_deletes_tracks_if_they_expire" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_clean_up_does_nothing_if_ttl_is_none" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_get_track_returns_track_if_it_exists" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_insert_or_update_sets_oldest_timestamp" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_n_latest_returns_empty_list_for_empty" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_n_latest_returns_tracks_in_correct_order" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_ordered_tracker_handles_cleanup" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_ordered_tracker_raises_value_error_if_older_record_is_received" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_ordered_tracker_returns_correct_latest_n" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_pop_tracks_returns_and_deletes_track_if_it_exists" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_poplast_is_non_destructive" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_tracker_works_as_context_manager" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_tracks_property_returns_tracks_as_list" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_update_always_updates_all_fields" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_update_cleans_up_afterwards" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_update_raises_error_is_timestamp_is_younger" time="0.000" /><testcase classname="tests.test_udp_stream.TestOutOfOrder" name="test_stream" time="0.101" /></testsuite></testsuites>
Agent log Included
  [   0.0s] 📋 System Prompt: You are a principal Python software engineer resolving a repository issue in `/workspace`.
  [   0.0s] 🎯 Task Prompt: You are evaluating a software engineering task for repository M0r13n/pyais.
  [  17.7s] 💭 (788 chars) "The user is reporting a bug in how communication status (SOTDMA vs ITDMA) is determined in 
`pyais`.
The current implemen..."
  [  17.7s] 🔧 run_command grep -rn --include="*.py" "is_sotdma" pyais/
  [  17.7s]   → pyais/messages.py:819:        if self.is_sotdma:
  [  24.2s] 💭 (115 chars) "Okay, it's in `pyais/messages.py`. Let's examine the code around `is_sotdma` and `is_itdma` 
in `pyais/messages.py`."
  [  24.2s] 📖 read_file (pyais/messages.py)
  [  24.2s]   ✓ {'status': 'ok', 'filepath': 'pyais/messages.py', 'content': '            
result.update(get_sotdma_comm_state(self.commu…
  [  37.9s] 💭 (297 chars) "Wait, `is_sotdma` and `is_itdma` are properties of a class. I need to know which class they 
belong to. Since they are at..."
  [  37.9s] 🔧 run_command grep -n "class " pyais/messages.py | grep -B 1 -A 1 "def is_sotdma" || grep -B 1
  [  38.0s]   → class CommunicationStateMixin:
  [  41.1s] 💭 (122 chars) "It looks like they are in `CommunicationStateMixin`. Let's find the 
`CommunicationStateMixin` class and see how it's use..."
  [  41.1s] 🔧 run_command grep -n "class CommunicationStateMixin" pyais/messages.py
  [  41.2s]   → 792:class CommunicationStateMixin:
  [  47.1s] 💭 (114 chars) "Now let's read `CommunicationStateMixin` and see what `self.radio` is and where 
`MAX_COMM_STATE_VALUE` is defined."
  [  47.1s] 📖 read_file (pyais/messages.py)
  [  47.1s]   ✓ {'status': 'ok', 'filepath': 'pyais/messages.py', 'content': 'class CommunicationStateMixin:\n    """\n 
Mixin class t…
  [  67.5s] 💭 (882 chars) "`MAX_COMM_STATE_VALUE = 0x7ffff` (which is 2^19 - 1, i.e., 19 ones).
So `self.radio <= 0x7ffff` means the 20th bit (bit ..."
  [  67.5s] 🔧 run_command grep -rn "CommunicationStateMixin" pyais/messages.py
  [  67.5s]   → 792:class CommunicationStateMixin:
  [  85.6s] 💭 (916 chars) "So we have:
- Type 1
- Type 4
- Type 9 (Edge case)
- Type 18 (Edge case)
- Type 26 (Edge case)

According to the problem..."
  [  85.6s] 🔧 run_command grep -rn "CommunicationStateMixin" pyais/messages.py
  [  85.7s]   → 792:class CommunicationStateMixin:
  [ 100.3s] 💭 (475 chars) "Hmm, only 1, 4, 9, 18, 26. Maybe Type 2 and 3 don't use `CommunicationStateMixin`? Or maybe 
they are defined differently..."
  [ 100.3s] 🔧 run_command (6 lines) python3 - <<'PY'
  [ 100.5s]   → SOTDMA: True, ITDMA: False
  [ 122.2s] 💭 (1057 chars) "The output says `SOTDMA: True, ITDMA: False`.
The problem states "incorrectly indicates that it is a SOTDMA communicatio..."
  [ 122.2s] 📖 read_file (pyais/messages.py)
  [ 122.2s]   ✓ {'status': 'ok', 'filepath': 'pyais/messages.py', 'content': 'class MessageType1(Payload, 
CommunicationStateMixin):\n   …
  [ 128.7s] 💭 (189 chars) "I don't see `radio` being defined in `MessageType1`. Where is `radio` coming from?
Wait, `Payload` might be doing someth..."
  [ 128.7s] 📖 read_file (pyais/messages.py)
  [ 128.7s]   ✓ {'status': 'ok', 'filepath': 'pyais/messages.py', 'content': '    lat = bit_field(27, float, 
from_converter=from_lat_lon…
  [ 177.7s] 💭 (2261 chars) "Okay, so `radio = bit_field(19, int, default=0, signed=False)` is defined in 
`MessageType1`.
The current `is_sotdma` and..."
  [ 177.7s] 📖 read_file (pyais/messages.py)
  [ 177.7s]   ✓ {'status': 'ok', 'filepath': 'pyais/messages.py', 'content': 'class MessageType9(Payload, 
CommunicationStateMixin):\n   …
  [ 185.7s] 💭 (281 chars) "Wait, `MessageType9` also doesn't have `radio` defined in the snippet. Let's read more of 
it.
Actually, `MessageType9` p..."
  [ 185.7s] 🔧 run_command grep -n "radio = bit_field" pyais/messages.py
  [ 185.7s]   → 869:    radio = bit_field(19, int, default=0, signed=False)
  [ 265.7s] 💭 (3924 chars) "Aha!
- Line 869 (MessageType1): `radio = bit_field(19, ...)`
- Line 910 (MessageType4): `radio = bit_field(19, ...)`
- L..."
  [ 265.7s] 🔧 run_command (5 lines) python3 - <<'PY'
  [ 265.8s]   → Type: 3
  [ 347.3s] 💭 (4202 chars) "Yes, it's Type 3. And for Type 3, it's ITDMA.
But the current output was `SOTDMA: True, ITDMA: False`.
So my analysis is..."
  [ 347.3s] 📖 read_file (pyais/messages.py)
  [ 347.3s]   ✓ {'status': 'ok', 'filepath': 'pyais/messages.py', 'content': '\n    @property\n    def 
communication_state_raw(self) -> …
  [ 381.7s] 💭 (1895 chars) "Okay, it's fine.

Now I'll apply the fix to `pyais/messages.py`.

I'll replace:
```python
    @property
    def is_sotdm..."
  [ 381.7s] 🔧 run_command grep -n "class Payload" pyais/messages.py
  [ 381.8s]   → 556:class Payload(abc.ABC):
  [ 385.1s] 💭 (38 chars) "Let's see if `Payload` has `msg_type`."
  [ 385.1s] 📖 read_file (pyais/messages.py)
  [ 385.1s]   ✓ {'status': 'ok', 'filepath': 'pyais/messages.py', 'content': 'class Payload(abc.ABC):\n    """\n    
Payload class\n    -…
  [ 430.3s] 💭 (1712 chars) "`Payload` doesn't have `msg_type`. But all the specific message types (1, 2, 3, 4, 9, 18, 
26) have it.
Since `Communicat..."
  [ 430.3s] ✏️ edit_file (pyais/messages.py)
  [ 430.4s]   ✓ {'status': 'ok', 'filepath': 'pyais/messages.py', 'occurrences': 1, 'strategy': 'exact', 'diff': '--- 
a/pyais/messages.p…
  [ 433.7s] 💭 (54 chars) "Now let's verify the fix with the reproduction script."
  [ 433.7s] 🔧 run_command (6 lines) python3 - <<'PY'
  [ 433.8s]   → SOTDMA: False, ITDMA: True
Native result row
{
  "agent_error": "Agent exceeded session timeout (7.5 min)",
  "duration_seconds": 460.55,
  "error": "Agent exceeded session timeout (7.5 min)",
  "grade": {
    "collected_tests": 302,
    "competition_junit_ok": true,
    "error": null,
    "required_issues_count": 0,
    "resolved": true,
    "status_counts": {
      "PASSED": 300,
      "SKIPPED": 2
    },
    "strict_clean_patch": true,
    "test_exit_code": 0
  },
  "instance_id": "M0r13n__pyais-137",
  "pass_idx": 0,
  "patch_chars": 996,
  "phase1_seconds": 458.35,
  "repo": "M0r13n/pyais",
  "request_log_path": "/kaggle-root/working/r31/rollouts_X/requests/M0r13n__pyais-137__p0__r6.jsonl",
  "resolved": true,
  "rollout_idx": 6,
  "run_key": "M0r13n__pyais-137__p0__r6",
  "strict_clean_patch": true,
  "tool_calls": 18,
  "total_llm_calls": 18,
  "trace_path": "/kaggle-root/working/r31/rollouts_X/traces/trace_M0r13n__pyais-137__p0__r6.json"
}