Skip to content

Commit a220f50

Browse files
committed
add r4uab proto early beta
1 parent b8406e8 commit a220f50

File tree

3 files changed

+144
-1
lines changed

3 files changed

+144
-1
lines changed

SatsDecoder/systems/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from SatsDecoder.systems.greencube import *
1717
from SatsDecoder.systems.ledsat import *
1818
from SatsDecoder.systems.lucky7 import *
19+
from SatsDecoder.systems.r4uab import *
1920
from SatsDecoder.systems.roseycubesat import *
2021
from SatsDecoder.systems.samsat_ion2 import *
2122
from SatsDecoder.systems.sharjahsat import *
@@ -37,6 +38,7 @@
3738
'dstar_one',
3839
'ledsat',
3940
'lucky7',
41+
'r4uab',
4042
'roseycubesat',
4143
'samsat_ion2',
4244
'sharjahsat',

SatsDecoder/systems/common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class Protocol:
7676
has_ax25 = 0
7777

7878
def __init__(self, ir: ImageReceiver = None):
79-
self.ir = isinstance(ir, ImageReceiver) and ir
79+
self.ir = isinstance(ir, ImageReceiver) and ir or None
8080

8181
@staticmethod
8282
def get_sender_callsign(data):

SatsDecoder/systems/r4uab.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Copyright (c) 2026. Alexander Baskikh
2+
#
3+
# MIT License (MIT), http://opensource.org/licenses/MIT
4+
# Full license can be found in the LICENSE-MIT file
5+
#
6+
# SPDX-License-Identifier: MIT
7+
8+
import pathlib
9+
10+
import construct
11+
12+
from SatsDecoder import utils
13+
from SatsDecoder.systems import ax25, common
14+
from SatsDecoder.systems.image_receiver import ImageReceiver
15+
16+
__all__ = 'R4uabProtocol',
17+
18+
proto_name = 'r4uab'
19+
20+
# marker-variants:
21+
# FF01: jpg 640x480
22+
# FF02: jpg 320x240
23+
# FF03: png 640x480
24+
# FF04: png 320x240
25+
26+
# ptype-variant
27+
# 00: jpg
28+
# 01: png
29+
ptype_suff = {
30+
0: '.jpg',
31+
1: '.png',
32+
}
33+
34+
FILETRANSFER_FILESIZE = 0x07
35+
FILETRANSFER_DATA = 0x08
36+
37+
ptype_map = {
38+
FILETRANSFER_FILESIZE: construct.Struct(
39+
'size' / construct.Int16ul,
40+
),
41+
FILETRANSFER_DATA: construct.Struct(
42+
'pnum' / construct.Int16ul,
43+
'data' / construct.GreedyBytes,
44+
),
45+
}
46+
47+
frame = construct.Struct(
48+
'hdr' / construct.Hex(construct.Const(0xC100E000, construct.Int32ub)),
49+
'marker' / construct.Hex(construct.Int8ub),
50+
'marker_variant' / construct.Hex(construct.Int8ub),
51+
'ptype' / construct.Hex(construct.Int8ub),
52+
'ptype_variant' / construct.Hex(construct.Int8ub),
53+
'payload' / construct.Switch(construct.this.ptype, ptype_map, default=construct.GreedyBytes),
54+
)
55+
56+
57+
class R4uabImageReceiver(ImageReceiver):
58+
DATA_LEN = 200
59+
60+
def __init__(self, outdir):
61+
super().__init__(outdir)
62+
self.last_transfer_id = None
63+
64+
def generate_fid(self, ptype_variant, t=None):
65+
if not (self.current_fid and self.merge_mode):
66+
self.current_fid = f'R4UAB_{self.strftime(t)}'
67+
return self.current_fid
68+
69+
def transfer_id_process(self, transfer_id, ptype_variant):
70+
if self.last_transfer_id != transfer_id:
71+
self.last_transfer_id = transfer_id
72+
self.suff = ptype_suff.get(ptype_variant, '.bin')
73+
return 1
74+
75+
def push_data(self, packet, t=None, **kw):
76+
if packet.marker != 0xFF:
77+
return
78+
79+
transfer_id = (packet.marker << 16) | (packet.marker_variant << 8) | packet.ptype_variant
80+
81+
if packet.ptype == FILETRANSFER_DATA:
82+
force = self.transfer_id_process(transfer_id, packet.ptype_variant)
83+
if not self.suff:
84+
self.suff = ptype_suff.get(packet.ptype_variant, '.bin')
85+
off = self.DATA_LEN * (packet.payload.pnum - 1)
86+
img = self.get_image(force, t=t, ptype_variant=packet.ptype_variant)
87+
with img.lock:
88+
img.push_data(off, packet.payload.data[:self.DATA_LEN])
89+
if off < img.first_data_offset:
90+
img.first_data_offset = off
91+
92+
elif packet.ptype == FILETRANSFER_FILESIZE:
93+
force = self.transfer_id_process(transfer_id, packet.ptype_variant)
94+
img = self.get_image(force, t=t, ptype_variant=packet.ptype_variant)
95+
with img.lock:
96+
img.has_starter = 1
97+
img.open().truncate(packet.payload.size)
98+
99+
else:
100+
return
101+
102+
return 1
103+
104+
105+
class R4uabProtocol(common.Protocol):
106+
107+
def __init__(self, outdir):
108+
super().__init__(R4uabImageReceiver(outdir))
109+
110+
def recognize(self, bb, t=None):
111+
try:
112+
packet = frame.parse(bb)
113+
except construct.ConstError:
114+
return
115+
except construct.StreamError as e:
116+
print(e)
117+
print(bb)
118+
return
119+
120+
x = self.ir.push_data(packet, t=t)
121+
if x:
122+
yield 'img', 'R4UAB-Sat', (x, self.ir.cur_img)
123+
124+
125+
if __name__ == '__main__':
126+
bb = (
127+
'C100E000FF010700848F0000',
128+
'C100E000FF0108000100FFD8FFDB0084000D09090B0A080D0B0A0B0E0E0D0F13201513121213271C1E17202E2931302E292D2C333A4A3E333646372C2D405741464C4E525352323E5A615A50604A51524F010E0E0E131113261515264F352D354F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4FFFC401A20000010501010101010100000000000000000102030405060708090A0B100002010303020403050504040000017D0102030004110512213141061351',
129+
'DEADBEEF',
130+
'C100E000FF0103E0'
131+
)
132+
for b in bb:
133+
b = bytes.fromhex(b)
134+
try:
135+
x = frame.parse(b)
136+
except construct.ConstError:
137+
continue
138+
print(x)
139+
transfer_id = (x.marker << 16) | (x.marker_variant << 8) | x.ptype_variant
140+
print(hex(transfer_id))
141+
print(f'{x.marker:02X}{x.marker_variant:02X}{x.ptype_variant:02X}')

0 commit comments

Comments
 (0)