Diskussion:Nova 35: Unterschied zwischen den Versionen
Aus FabLab Region Nürnberg
Mose (Diskussion | Beiträge) |
Fbl (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
(13 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt) | |||
Zeile 8: | Zeile 8: | ||
* [https://stefan.schuermans.info/rdcam/ RDCAM Reverse Engineering] | * [https://stefan.schuermans.info/rdcam/ RDCAM Reverse Engineering] | ||
* [http://www.thunderlaser.co.nz/beifen/technical-support/downloads/category/rdc6442g-and-rdwork-software.html RDworks + Manuals] | * [http://www.thunderlaser.co.nz/beifen/technical-support/downloads/category/rdc6442g-and-rdwork-software.html RDworks + Manuals] | ||
* [https://github.com/t-oster/VisiCut/issues/404 VisiCut issue] | |||
* [https://edutechwiki.unige.ch/en/Ruida Protocol analysis] | |||
== Protokollanalyse == | == Protokollanalyse == | ||
=== UDP === | |||
* The device listens on a fixed UDP port (DEST_PORT). IPaddress is configurable 172.16.18.11, netmask is 255.255.255.0 fixed, Gateway is configurable 172.16.20.134. | |||
* An RD file is transfered as payload, same commands and syntax as with USB-Serial or USB-MassStorage. | |||
* The payload is split in chunks with a well known maximum size (MTU). (The last packet is usually shorter) | |||
* There is no header, and no arbitration phase, but successful transmission of the first chunk indicates device ready. | |||
* Each chunk starts with a two byte checksum, followed by payload data. Length of the payload is implicit by the UDP datagram size. (Would not work with TCP) | |||
* Each chunk is acknowledged with a single byte response packet: | |||
** 0xc6 if all is well, The next chunk should be sent. A delay of 4 seconds was tested successfully. | |||
** 0x46 if error. TODO: Checksum error and/or busy? A running laser job does not cause a busy condition. | |||
* The first chunk should be retried when 0x46 was received. For subsequent chunks transmission should be aborted. | |||
* No pause is needed after the last chunk. The payload contains a terminator token. | |||
Command line tool usage: | |||
wget https://github.com/jnweiger/ruida-laser/blob/master/udpsendruida.py | |||
python3 udpsendruida.py 192.168.1.100 thunder.rd | |||
=== earlier analysis === | |||
# Capture mit Wireshark | # Capture mit Wireshark | ||
Zeile 21: | Zeile 42: | ||
=== rdcam.py === | === rdcam.py === | ||
< | This python code snippet is probably useful with wireshark. Please add details. | ||
</ | <pre> | ||
def send_command(payload): | |||
data=scramble_bytes(payload) | |||
checksum=sum(data) | |||
b1=checksum&0xff | |||
b0=(checksum>>8)&0xff | |||
return bytes([b0,b1])+data | |||
def encode_number(n,l=5): | |||
res=[] | |||
while n>0: | |||
res.append(n&0x7f) | |||
n>>=7 | |||
while len(res)<l: | |||
res.append(0) | |||
res.reverse() | |||
return bytes(res) | |||
def decode_number(x): | |||
fak=1 | |||
res=0 | |||
for b in reversed(x): | |||
res+=fak*b | |||
fak*=0x80 | |||
return res | |||
def format_capture(c): | |||
for p in c: | |||
direction=p["_source"]['layers']['udp']['udp.port']=='50200' | |||
data=unscramble_packet(p,checksum=direction) | |||
line='-> ' if direction else '<- ' | |||
line+=' '.join([bytes(x).hex() for x in split_messages(data)]) | |||
print(line) | |||
def split_messages(d): | |||
m=[] | |||
res=[m] | |||
for x in d: | |||
if x&0x80: | |||
m=[] | |||
res.append(m) | |||
m.append(x) | |||
return res | |||
def unscramble_packet(p,checksum=False): | |||
string=p["_source"]["layers"]["data"]["data.data_raw"] | |||
if checksum: | |||
return list(bytes.fromhex(string[:4]))+unscramble_string(string[4:]) | |||
else: | |||
return unscramble_string(string) | |||
def unscramble_string(s): | |||
return [unscramble(b) for b in bytes.fromhex(s)] | |||
def unscramble(b): | |||
res_b=b-1 | |||
if res_b<0: res_b+=0x100 | |||
res_b^=0x88 | |||
fb=res_b&0x80 | |||
lb=res_b&1 | |||
res_b=res_b-fb-lb | |||
res_b|=lb<<7 | |||
res_b|=fb>>7 | |||
return res_b | |||
def scramble(b): | |||
fb=b&0x80 | |||
lb=b&1 | |||
res_b=b-fb-lb | |||
res_b|=lb<<7 | |||
res_b|=fb>>7 | |||
res_b^=0x88 | |||
res_b+=1 | |||
if res_b>0xff:res_b-=0x100 | |||
return res_b | |||
def scramble_bytes(bs): | |||
return bytes([scramble(b) for b in bs]) | |||
def unscramble_bytes(bs): | |||
return bytes([unscramble(b) for b in bs]) | |||
</pre> | |||
=== Data format === | === Data format === | ||
Byte = 1 Bit Message Start Indicator + 7 Bit Payload | * Byte = 1 Bit Message Start Indicator + 7 Bit Payload | ||
* Only one message (checksum + command) can be sent per UDP package | |||
* Max UDP package size 1472 bytes including checksum; fragmented by simple cutting (even inside a command) | |||
==== Checksum ==== | ==== Checksum ==== | ||
Zeile 33: | Zeile 138: | ||
2 Bytes - sum of scrambled message bytes; MSB first. | 2 Bytes - sum of scrambled message bytes; MSB first. | ||
Checksum has to be send before message. | Checksum has to be send before message. | ||
'''Network only''' USB does not have checksum. | |||
==== Values ==== | ==== Values ==== | ||
Zeile 58: | Zeile 165: | ||
! Byte squence !! Description !! how sure we are | ! Byte squence !! Description !! how sure we are | ||
|- | |- | ||
| C6 01 [[#VAL-POWER|POWER]] || 1st laser source min power || 99% | | C6 01 [[#VAL-POWER|<POWER>]] || 1st laser source min power || 99% | ||
|- | |- | ||
| C6 21 [[#VAL-POWER|POWER]] || 2nd laser source min power || 99% | | C6 21 [[#VAL-POWER|<POWER>]] || 2nd laser source min power || 99% | ||
|- | |- | ||
| C6 02 [[#VAL-POWER|POWER]] || 1st laser source max power || 99% | | C6 02 [[#VAL-POWER|<POWER>]] || 1st laser source max power || 99% | ||
|- | |- | ||
| C6 22 [[#VAL-POWER|POWER]] || 2nd laser source max power || 99% | | C6 22 [[#VAL-POWER|<POWER>]] || 2nd laser source max power || 99% | ||
|- | |- | ||
| C9 02 [[#VAL-SPEED|SPEED]] || movement and/or (not sure) cutting speed || 80% | | C9 02 [[#VAL-SPEED|<SPEED>]] || movement and/or (not sure) cutting speed || 80% | ||
|- | |- | ||
| D9 00 02 [[#VAL-ABSCOORD|ABSCOORD]] || move X || 99% | | D9 00 02 [[#VAL-ABSCOORD|<ABSCOORD>]] || move X || 99% | ||
|- | |- | ||
| D9 00 03 [[#VAL-ABSCOORD|ABSCOORD]] || move Y || 50% | | D9 00 03 [[#VAL-ABSCOORD|<ABSCOORD>]] || move Y || 50% | ||
|- | |- | ||
| D9 00 04 [[#VAL-ABSCOORD|ABSCOORD]] || move Z || 50% | | D9 00 04 [[#VAL-ABSCOORD|<ABSCOORD>]] || move Z || 50% | ||
|- | |- | ||
| D9 00 05 [[#VAL-ABSCOORD|ABSCOORD]] || move U || 50% | | D9 00 05 [[#VAL-ABSCOORD|<ABSCOORD>]] || move U || 50% | ||
|- | |- | ||
| CC || ACK from machine || 99% | | CC || ACK from machine || 99% | ||
|- | |- | ||
| CD || | | CD || ERR from machine || 99% | ||
|- | |||
| DA 00 XX XX || get XX XX from machine || 99% | |||
|- | |||
| DA 00 04 05 || saved job count || 99% | |||
|- | |||
| DA 01 XX XX <VALUE> || response to DA 00 XX XX || 99% | |||
|- | |||
| A8 [[#VAL-ABSCOORD|<ABSCOORD>]] [[#VAL-ABSCOORD|<ABSCOORD>]] || Straight cut to absolute X Y; turn laser on with configured speed and power || 99% | |||
|- | |||
| A9 [[#VAL-RELCOORD|<RELCOORD>]] [[#VAL-RELCOORD|<RELCOORD>]] || Straight cut to relative X Y; turn laser on with configured speed and power || 99% | |||
|- | |- | ||
| | | E7 50 [[#VAL-ABSCOORD|<ABSCOORD>]] [[#VAL-ABSCOORD|<ABSCOORD>]] || Bounding box top left? || 30% | ||
|- | |- | ||
| | | E7 51 [[#VAL-ABSCOORD|<ABSCOORD>]] [[#VAL-ABSCOORD|<ABSCOORD>]] || Bounding box bottom right? || 30% | ||
|- | |- | ||
| E7 | | E8 02 E7 01 [[#VAL-CSTRING|<CSTRING>]] || Set filename for following transfer (transfer needs to be done really quickly after this!) || 90% | ||
|- | |- | ||
| | | E8 01 XX XX || Read filename number XX XX || | ||
|- | |- | ||
| | | 88 [[#VAL-ABSCOORD|<ABSCOORD>]] [[#VAL-ABSCOORD|<ABSCOORD>]] || straight move to absolute X Y as fast as possible; with laser off || 99% | ||
|- | |- | ||
| | | 89 [[#VAL-RELCOORD|<RELCOORD>]] [[#VAL-RELCOORD|<RELCOORD>]] || straight move to relative X Y as fast as possible; with laser off || 80% | ||
|} | |} |
Aktuelle Version vom 10. November 2023, 18:30 Uhr
Hardware
- RDC6442G Herstellerseite
- Network aware laser cutter security
- RDCAM Reverse Engineering
- RDworks + Manuals
- VisiCut issue
Protokollanalyse
UDP
- The device listens on a fixed UDP port (DEST_PORT). IPaddress is configurable 172.16.18.11, netmask is 255.255.255.0 fixed, Gateway is configurable 172.16.20.134.
- An RD file is transfered as payload, same commands and syntax as with USB-Serial or USB-MassStorage.
- The payload is split in chunks with a well known maximum size (MTU). (The last packet is usually shorter)
- There is no header, and no arbitration phase, but successful transmission of the first chunk indicates device ready.
- Each chunk starts with a two byte checksum, followed by payload data. Length of the payload is implicit by the UDP datagram size. (Would not work with TCP)
- Each chunk is acknowledged with a single byte response packet:
- 0xc6 if all is well, The next chunk should be sent. A delay of 4 seconds was tested successfully.
- 0x46 if error. TODO: Checksum error and/or busy? A running laser job does not cause a busy condition.
- The first chunk should be retried when 0x46 was received. For subsequent chunks transmission should be aborted.
- No pause is needed after the last chunk. The payload contains a terminator token.
Command line tool usage:
wget https://github.com/jnweiger/ruida-laser/blob/master/udpsendruida.py python3 udpsendruida.py 192.168.1.100 thunder.rd
earlier analysis
- Capture mit Wireshark
- Datei > Paketdissektion exportieren > Als JSON
- Packet summary line
- Include column headings
- Packet details:
- As displayed
- Packet Bytes
- Packet summary line
- some more pat foo
rdcam.py
This python code snippet is probably useful with wireshark. Please add details.
def send_command(payload): data=scramble_bytes(payload) checksum=sum(data) b1=checksum&0xff b0=(checksum>>8)&0xff return bytes([b0,b1])+data def encode_number(n,l=5): res=[] while n>0: res.append(n&0x7f) n>>=7 while len(res)<l: res.append(0) res.reverse() return bytes(res) def decode_number(x): fak=1 res=0 for b in reversed(x): res+=fak*b fak*=0x80 return res def format_capture(c): for p in c: direction=p["_source"]['layers']['udp']['udp.port']=='50200' data=unscramble_packet(p,checksum=direction) line='-> ' if direction else '<- ' line+=' '.join([bytes(x).hex() for x in split_messages(data)]) print(line) def split_messages(d): m=[] res=[m] for x in d: if x&0x80: m=[] res.append(m) m.append(x) return res def unscramble_packet(p,checksum=False): string=p["_source"]["layers"]["data"]["data.data_raw"] if checksum: return list(bytes.fromhex(string[:4]))+unscramble_string(string[4:]) else: return unscramble_string(string) def unscramble_string(s): return [unscramble(b) for b in bytes.fromhex(s)] def unscramble(b): res_b=b-1 if res_b<0: res_b+=0x100 res_b^=0x88 fb=res_b&0x80 lb=res_b&1 res_b=res_b-fb-lb res_b|=lb<<7 res_b|=fb>>7 return res_b def scramble(b): fb=b&0x80 lb=b&1 res_b=b-fb-lb res_b|=lb<<7 res_b|=fb>>7 res_b^=0x88 res_b+=1 if res_b>0xff:res_b-=0x100 return res_b def scramble_bytes(bs): return bytes([scramble(b) for b in bs]) def unscramble_bytes(bs): return bytes([unscramble(b) for b in bs])
Data format
- Byte = 1 Bit Message Start Indicator + 7 Bit Payload
- Only one message (checksum + command) can be sent per UDP package
- Max UDP package size 1472 bytes including checksum; fragmented by simple cutting (even inside a command)
Checksum
2 Bytes - sum of scrambled message bytes; MSB first.
Checksum has to be send before message.
Network only USB does not have checksum.
Values
Value | Lenght | Description |
---|---|---|
ABSCOORD | 5 Bytes | absolute position relative to job origin in µm |
RELCOORD | 2 Bytes | relative position in µm; signed (2s complement) |
SPEED | 5 Bytes | speed in µm/s |
POWER | 2 Bytes | power in 0,006103516% (100/2^14) |
CSTRING | variable zero terminated |
Commands
Byte squence | Description | how sure we are |
---|---|---|
C6 01 <POWER> | 1st laser source min power | 99% |
C6 21 <POWER> | 2nd laser source min power | 99% |
C6 02 <POWER> | 1st laser source max power | 99% |
C6 22 <POWER> | 2nd laser source max power | 99% |
C9 02 <SPEED> | movement and/or (not sure) cutting speed | 80% |
D9 00 02 <ABSCOORD> | move X | 99% |
D9 00 03 <ABSCOORD> | move Y | 50% |
D9 00 04 <ABSCOORD> | move Z | 50% |
D9 00 05 <ABSCOORD> | move U | 50% |
CC | ACK from machine | 99% |
CD | ERR from machine | 99% |
DA 00 XX XX | get XX XX from machine | 99% |
DA 00 04 05 | saved job count | 99% |
DA 01 XX XX <VALUE> | response to DA 00 XX XX | 99% |
A8 <ABSCOORD> <ABSCOORD> | Straight cut to absolute X Y; turn laser on with configured speed and power | 99% |
A9 <RELCOORD> <RELCOORD> | Straight cut to relative X Y; turn laser on with configured speed and power | 99% |
E7 50 <ABSCOORD> <ABSCOORD> | Bounding box top left? | 30% |
E7 51 <ABSCOORD> <ABSCOORD> | Bounding box bottom right? | 30% |
E8 02 E7 01 <CSTRING> | Set filename for following transfer (transfer needs to be done really quickly after this!) | 90% |
E8 01 XX XX | Read filename number XX XX | |
88 <ABSCOORD> <ABSCOORD> | straight move to absolute X Y as fast as possible; with laser off | 99% |
89 <RELCOORD> <RELCOORD> | straight move to relative X Y as fast as possible; with laser off | 80% |