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% |