Skip to content

Commit aa3ebef

Browse files
authored
Document all known VSFRs (#47)
clean up VS (VString) class - Radiacode apps refer to these by their hex values, not decimal. - Add some other types I found in my logs. Prefer hex notation rather than decimal, to align with radiacode app debug output which also uses hex. Replace magic numbers with their named versions
1 parent 0335e61 commit aa3ebef

File tree

2 files changed

+160
-67
lines changed

2 files changed

+160
-67
lines changed

radiacode/radiacode.py

+29-18
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,28 @@
66
"""
77

88
import datetime
9-
import struct
109
import platform
10+
import struct
1111
from typing import Optional
1212

1313
from radiacode.bytes_buffer import BytesBuffer
1414
from radiacode.decoders.databuf import decode_VS_DATA_BUF
1515
from radiacode.decoders.spectrum import decode_RC_VS_SPECTRUM
1616
from radiacode.transports.bluetooth import Bluetooth
1717
from radiacode.transports.usb import Usb
18-
from radiacode.types import CTRL, VS, VSFR, DisplayDirection, DoseRateDB, Event, RareData, RawData, RealTimeData, Spectrum
18+
from radiacode.types import (
19+
COMMAND,
20+
CTRL,
21+
VS,
22+
VSFR,
23+
DisplayDirection,
24+
DoseRateDB,
25+
Event,
26+
RareData,
27+
RawData,
28+
RealTimeData,
29+
Spectrum,
30+
)
1931

2032

2133
def spectrum_channel_to_energy(channel_number: int, a0: float, a1: float, a2: float) -> float:
@@ -76,7 +88,7 @@ def __init__(
7688
self._connection = Usb(serial_number=serial_number)
7789

7890
# init
79-
self.execute(b'\x07\x00', b'\x01\xff\x12\xff')
91+
self.execute(COMMAND.SET_EXCHANGE, b'\x01\xff\x12\xff')
8092
self.set_local_time(datetime.datetime.now())
8193
self.device_time(0)
8294
self._base_time = datetime.datetime.now() + datetime.timedelta(seconds=128)
@@ -96,12 +108,11 @@ def __init__(
96108
def base_time(self) -> datetime.datetime:
97109
return self._base_time
98110

99-
def execute(self, reqtype: bytes, args: Optional[bytes] = None) -> BytesBuffer:
100-
assert len(reqtype) == 2
111+
def execute(self, reqtype: COMMAND, args: Optional[bytes] = None) -> BytesBuffer:
101112
req_seq_no = 0x80 + self._seq
102113
self._seq = (self._seq + 1) % 32
103114

104-
req_header = reqtype + b'\x00' + struct.pack('<B', req_seq_no)
115+
req_header = struct.pack('<HBB', int(reqtype), 0, req_seq_no)
105116
request = req_header + (args or b'')
106117
full_request = struct.pack('<I', len(request)) + request
107118

@@ -111,7 +122,7 @@ def execute(self, reqtype: bytes, args: Optional[bytes] = None) -> BytesBuffer:
111122
return response
112123

113124
def read_request(self, command_id: int | VS | VSFR) -> BytesBuffer:
114-
r = self.execute(b'\x26\x08', struct.pack('<I', int(command_id)))
125+
r = self.execute(COMMAND.RD_VIRT_STRING, struct.pack('<I', int(command_id)))
115126
retcode, flen = r.unpack('<II')
116127
assert retcode == 1, f'{command_id}: got retcode {retcode}'
117128
# HACK: workaround for new firmware bug(?)
@@ -122,20 +133,20 @@ def read_request(self, command_id: int | VS | VSFR) -> BytesBuffer:
122133
return r
123134

124135
def write_request(self, command_id: int | VSFR, data: Optional[bytes] = None) -> None:
125-
r = self.execute(b'\x25\x08', struct.pack('<I', int(command_id)) + (data or b''))
136+
r = self.execute(COMMAND.WR_VIRT_SFR, struct.pack('<I', int(command_id)) + (data or b''))
126137
retcode = r.unpack('<I')[0]
127138
assert retcode == 1
128139
assert r.size() == 0
129140

130141
def batch_read_vsfrs(self, vsfr_ids: list[VSFR]) -> list[int]:
131142
assert len(vsfr_ids)
132-
r = self.execute(b'\x2a\x08', b''.join(struct.pack('<I', int(c)) for c in vsfr_ids))
143+
r = self.execute(COMMAND.RD_VIRT_SFR_BATCH, b''.join(struct.pack('<I', int(c)) for c in vsfr_ids))
133144
ret = [r.unpack('<I')[0] for _ in range(len(vsfr_ids))]
134145
assert r.size() == 0
135146
return ret
136147

137148
def status(self) -> str:
138-
r = self.execute(b'\x05\x00')
149+
r = self.execute(COMMAND.GET_STATUS)
139150
flags = r.unpack('<I')
140151
assert r.size() == 0
141152
return f'status flags: {flags}'
@@ -149,10 +160,10 @@ def set_local_time(self, dt: datetime.datetime) -> None:
149160
Microseconds are ignored.
150161
"""
151162
d = struct.pack('<BBBBBBBB', dt.day, dt.month, dt.year - 2000, 0, dt.second, dt.minute, dt.hour, 0)
152-
self.execute(b'\x04\x0a', d)
163+
self.execute(COMMAND.SET_TIME, d)
153164

154165
def fw_signature(self) -> str:
155-
r = self.execute(b'\x01\x01')
166+
r = self.execute(COMMAND.FW_SIGNATURE)
156167
signature = r.unpack('<I')[0]
157168
filename = r.unpack_string()
158169
idstring = r.unpack_string()
@@ -166,7 +177,7 @@ def fw_version(self) -> tuple[tuple[int, int, str], tuple[int, int, str]]:
166177
- Boot version: (major, minor, date string)
167178
- Target version: (major, minor, date string)
168179
"""
169-
r = self.execute(b'\x0a\x00')
180+
r = self.execute(COMMAND.GET_VERSION)
170181
boot_minor, boot_major = r.unpack('<HH')
171182
boot_date = r.unpack_string()
172183
target_minor, target_major = r.unpack('<HH')
@@ -181,7 +192,7 @@ def hw_serial_number(self) -> str:
181192
str: Hardware serial number formatted as hyphen-separated hexadecimal groups
182193
(e.g. "12345678-9ABCDEF0")
183194
"""
184-
r = self.execute(b'\x0b\x00')
195+
r = self.execute(COMMAND.GET_SERIAL)
185196
serial_len = r.unpack('<I')[0]
186197
assert serial_len % 4 == 0
187198
serial_groups = [r.unpack('<I')[0] for _ in range(serial_len // 4)]
@@ -202,11 +213,11 @@ def serial_number(self) -> str:
202213
Returns:
203214
str: The device serial number as an ASCII string
204215
"""
205-
r = self.read_request(8)
216+
r = self.read_request(VS.SERIAL_NUMBER)
206217
return r.data().decode('ascii')
207218

208219
def commands(self) -> str:
209-
br = self.read_request(257)
220+
br = self.read_request(VS.SFR_FILE)
210221
return br.data().decode('ascii')
211222

212223
# called with 0 after init!
@@ -254,7 +265,7 @@ def spectrum_reset(self) -> None:
254265
This clears the current spectrum data buffer, effectively resetting the spectrum
255266
measurement to start fresh.
256267
"""
257-
r = self.execute(b'\x27\x08', struct.pack('<II', int(VS.SPECTRUM), 0))
268+
r = self.execute(COMMAND.WR_VIRT_STRING, struct.pack('<II', int(VS.SPECTRUM), 0))
258269
retcode = r.unpack('<I')[0]
259270
assert retcode == 1
260271
assert r.size() == 0
@@ -282,7 +293,7 @@ def set_energy_calib(self, coef: list[float]) -> None:
282293
"""
283294
assert len(coef) == 3
284295
pc = struct.pack('<fff', *coef)
285-
r = self.execute(b'\x27\x08', struct.pack('<II', int(VS.ENERGY_CALIB), len(pc)) + pc)
296+
r = self.execute(COMMAND.WR_VIRT_STRING, struct.pack('<II', int(VS.ENERGY_CALIB), len(pc)) + pc)
286297
retcode = r.unpack('<I')[0]
287298
assert retcode == 1
288299

radiacode/types.py

+131-49
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class RareData:
8484

8585

8686
class EventId(Enum):
87+
POWER_OFF = 0
88+
POWER_ON = 1
8789
TOGGLE_SIGNAL = 3
8890
DOSE_RESET = 4
8991
BATTERY_FULL = 7
@@ -92,6 +94,8 @@ class EventId(Enum):
9294
DOSE_RATE_ALARM2 = 10
9395
DOSE_ALARM1 = 12
9496
DOSE_ALARM2 = 13
97+
TEXT_MESSAGE = 17
98+
SPECTRUM_RESET = 19
9599
COUNT_RATE_ALARM1 = 20
96100
COUNT_RATE_ALARM2 = 21
97101

@@ -145,62 +149,119 @@ def __int__(self) -> int:
145149

146150

147151
class VSFR(Enum):
148-
DEVICE_CTRL = 1280
149-
DEVICE_ON = 1283
150-
DEVICE_LANG = 1282
151-
DEVICE_TIME = 1284
152-
DISP_CTRL = 1296
153-
DISP_BRT = 1297
154-
DISP_CONTR = 1298
155-
DISP_OFF_TIME = 1299
156-
DISP_ON = 1300
157-
DISP_DIR = 1301
158-
SOUND_CTRL = 1312
159-
SOUND_VOL = 1313
160-
SOUND_ON = 1314
161-
SOUND_BUTTON = 1315
162-
PLAY_SIGNAL = 1505
163-
VIBRO_CTRL = 1328
164-
VIBRO_ON = 1329
165-
LEDS_CTRL = 1344
166-
LED0_BRT = 1345
167-
LED1_BRT = 1346
168-
LED2_BRT = 1347
169-
LED3_BRT = 1348
170-
LEDS_ON = 1349
171-
ALARM_MODE = 1504
172-
MS_CTRL = 1536
173-
MS_MODE = 1537
174-
MS_SUB_MODE = 1538
175-
MS_RUN = 1539
176-
DOSE_RESET = 32775
177-
# CR_LEV1_cp10s
178-
# CR_LEV2_cp10s
179-
# CR_UNITS
180-
# DR_LEV1_uR_h
181-
# DR_LEV2_uR_h
182-
# DS_LEV1_uR
183-
# DS_LEV2_uR
184-
# DS_UNITS
185-
# RAW_FILTER
186-
# SYS_FW_VER_BT
152+
DEVICE_CTRL = 0x0500
153+
DEVICE_LANG = 0x0502
154+
DEVICE_ON = 0x0503
155+
DEVICE_TIME = 0x0504
156+
157+
DISP_CTRL = 0x0510
158+
DISP_BRT = 0x0511
159+
DISP_CONTR = 0x0512
160+
DISP_OFF_TIME = 0x0513
161+
DISP_ON = 0x0514
162+
DISP_DIR = 0x0515
163+
DISP_BACKLT_ON = 0x0516
164+
165+
SOUND_CTRL = 0x0520
166+
SOUND_VOL = 0x0521
167+
SOUND_ON = 0x0522
168+
SOUND_BUTTON = 0x0523
169+
170+
VIBRO_CTRL = 0x0530
171+
VIBRO_ON = 0x0531
172+
173+
LEDS_CTRL = 0x0540
174+
LED0_BRT = 0x0541
175+
LED1_BRT = 0x0542
176+
LED2_BRT = 0x0543
177+
LED3_BRT = 0x0544
178+
LEDS_ON = 0x0545
179+
180+
ALARM_MODE = 0x05E0
181+
PLAY_SIGNAL = 0x05E1
182+
183+
MS_CTRL = 0x0600
184+
MS_MODE = 0x0601
185+
MS_SUB_MODE = 0x0602
186+
MS_RUN = 0x0603
187+
188+
BLE_TX_PWR = 0x0700
189+
190+
DR_LEV1_uR_h = 0x8000
191+
DR_LEV2_uR_h = 0x8001
192+
DS_LEV1_100uR = 0x8002
193+
DS_LEV2_100uR = 0x8003
194+
DS_UNITS = 0x8004
195+
CPS_FILTER = 0x8005
196+
RAW_FILTER = 0x8006
197+
DOSE_RESET = 0x8007
198+
CR_LEV1_cp10s = 0x8008
199+
CR_LEV2_cp10s = 0x8009
200+
201+
USE_nSv_h = 0x800C
202+
203+
CHN_TO_keV_A0 = 0x8010
204+
CHN_TO_keV_A1 = 0x8011
205+
CHN_TO_keV_A2 = 0x8012
206+
CR_UNITS = 0x8013
207+
DS_LEV1_uR = 0x8014
208+
DS_LEV2_uR = 0x8015
209+
210+
CPS = 0x8020
211+
DR_uR_h = 0x8021
212+
DS_uR = 0x8022
213+
214+
TEMP_degC = 0x8024
215+
ACC_X = 0x8025
216+
ACC_Y = 0x8026
217+
ACC_Z = 0x8027
218+
OPT = 0x8028
219+
220+
RAW_TEMP_degC = 0x8033
221+
TEMP_UP_degC = 0x8034
222+
TEMP_DN_degC = 0x8035
223+
224+
VBIAS_mV = 0xC000
225+
COMP_LEV = 0xC001
226+
CALIB_MODE = 0xC002
227+
DPOT_RDAC = 0xC004
228+
DPOT_RDAC_EEPROM = 0xC005
229+
DPOT_TOLER = 0xC006
230+
231+
SYS_MCU_ID0 = 0xFFFF0000
232+
SYS_MCU_ID1 = 0xFFFF0001
233+
SYS_MCU_ID2 = 0xFFFF0002
234+
235+
SYS_DEVICE_ID = 0xFFFF0005
236+
SYS_SIGNATURE = 0xFFFF0006
237+
SYS_RX_SIZE = 0xFFFF0007
238+
SYS_TX_SIZE = 0xFFFF0008
239+
SYS_BOOT_VERSION = 0xFFFF0009
240+
SYS_TARGET_VERSION = 0xFFFF000A
241+
SYS_STATUS = 0xFFFF000B
242+
SYS_MCU_VREF = 0xFFFF000C
243+
SYS_MCU_TEMP = 0xFFFF000D
244+
SYS_FW_VER_BT = 0xFFFF010
187245

188246
def __int__(self) -> int:
189247
return self.value
190248

191249

192250
class VS(Enum):
193251
CONFIGURATION = 2
194-
TEXT_MESSAGE = 15
195-
DATA_BUF = 256
196-
SPECTRUM = 512
197-
ENERGY_CALIB = 514
198-
SPEC_ACCUM = 517
199-
SPEC_DIFF = 518 # TODO: what's that? Can be decoded by spectrum decoder
200-
SPEC_RESET = 519 # TODO: looks like spectrum, but our spectrum decoder fails with `vlen == 7 unsupported`
201-
MEM_SNAPSHOT = 224
202-
# UNKNOWN_13 = 13
203-
# UNKNOWN_240 = 240
252+
FW_DESCRIPTOR = 3
253+
SERIAL_NUMBER = 8
254+
# UNKNOWN_13 = 0xd
255+
TEXT_MESSAGE = 0xF
256+
MEM_SNAPSHOT = 0xE0
257+
# UNKNOWN_240 = 0xf0
258+
DATA_BUF = 0x100
259+
SFR_FILE = 0x101
260+
SPECTRUM = 0x200
261+
ENERGY_CALIB = 0x202
262+
SPEC_ACCUM = 0x205
263+
SPEC_DIFF = 0x206 # TODO: what's that? Can be decoded by spectrum decoder
264+
SPEC_RESET = 0x207 # TODO: looks like spectrum, but our spectrum decoder fails with `vlen == 7 unsupported`
204265

205266
def __int__(self) -> int:
206267
return self.value
@@ -218,3 +279,24 @@ class CTRL(Enum):
218279

219280
def __int__(self) -> int:
220281
return self.value
282+
283+
284+
class COMMAND(Enum):
285+
GET_STATUS = 0x0005
286+
SET_EXCHANGE = 0x0007
287+
GET_VERSION = 0x000A
288+
GET_SERIAL = 0x000B
289+
FW_IMAGE_GET_INFO = 0x0012
290+
FW_SIGNATURE = 0x0101
291+
RD_HW_CONFIG = 0x0807
292+
RD_VIRT_SFR = 0x0824
293+
WR_VIRT_SFR = 0x0825
294+
RD_VIRT_STRING = 0x0826
295+
WR_VIRT_STRING = 0x0827
296+
RD_VIRT_SFR_BATCH = 0x082A
297+
WR_VIRT_SFR_BATCH = 0x082B
298+
RD_FLASH = 0x081C
299+
SET_TIME = 0x0A04
300+
301+
def __int__(self) -> int:
302+
return self.value

0 commit comments

Comments
 (0)