Difference between revisions of "GE Medical Flashpad Digital Xray Detector"
Gamerpaddy (talk | contribs) |
Gamerpaddy (talk | contribs) |
||
| (10 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
==Overview== | ==Overview== | ||
| + | {{Note|UNFINISHED Project, it was not yet possible to retrieve a exposure or dark frame in my testing, further investigation needed. any help is appreciated on recessim discord.}} | ||
The '''GE Flashpad''' is a Digital Radiography image sensor from approximately 2010, originally used in the '''GE Optima 220AMX''' mobile X-ray unit. It was designed to replace analog film in radiology, dramatically reducing image acquisition time from hours to seconds. | The '''GE Flashpad''' is a Digital Radiography image sensor from approximately 2010, originally used in the '''GE Optima 220AMX''' mobile X-ray unit. It was designed to replace analog film in radiology, dramatically reducing image acquisition time from hours to seconds. | ||
| Line 8: | Line 9: | ||
{{Note|All findings on this page are based on a single unit and the time spent working on it. Information is subject to speculation and may not be fully accurate.}} | {{Note|All findings on this page are based on a single unit and the time spent working on it. Information is subject to speculation and may not be fully accurate.}} | ||
| − | {{Note | + | {{Note|AI assistance was used in writing this page for improved formatting and readability, as well as in the process of finding information, testing, and analyzing firmware dumps and backups.}} |
| − | == Purpose and Motivation == | + | |
| + | All files used and mentioned are found on the official Optima XR220amx Application Software and Linux OS DVD 5406106-10 Rev3, which i cannot provide due to being copyrighted content. | ||
| + | <br /> | ||
| + | ==Purpose and Motivation== | ||
This project documents the reverse engineering of the GE FlashPad wireless | This project documents the reverse engineering of the GE FlashPad wireless | ||
digital radiography detector used with the Optima XR200/220 AMX mobile X-ray | digital radiography detector used with the Optima XR200/220 AMX mobile X-ray | ||
system, with the goal of freeing these detectors for independent use. | system, with the goal of freeing these detectors for independent use. | ||
| − | [[File:Xray capture pcb.png|thumb|Xray capture of a PCB for Reverse Engineering]] | + | [[File:Xray capture pcb.png|thumb|Xray capture of a PCB for Reverse Engineering using a different detector]] |
| − | Large numbers of these detectors reach the used market, but each one is | + | Large numbers of these detectors reach the used market, but each one is bound to its original Optima 220 AMX |
| − | |||
console. Once separated from that console, or once the console is | console. Once separated from that console, or once the console is | ||
decommissioned, the detector is effectively useless even though the hardware is | decommissioned, the detector is effectively useless even though the hardware is | ||
| − | fully functional. The aim of this work is to remove that artificial barrier so | + | fully functional. There is a Windows software but it is not obtainable. The aim of this work is to remove that artificial barrier so |
that a standalone FlashPad can be paired, configured, and read out by any host, | that a standalone FlashPad can be paired, configured, and read out by any host, | ||
without the half million dollar console it was sold with. | without the half million dollar console it was sold with. | ||
| Line 26: | Line 29: | ||
The intended beneficiaries are: | The intended beneficiaries are: | ||
| − | * Hobbyists, researchers, and engineers who want a high quality flat panel detector for their own imaging projects. | + | *Hobbyists, researchers, and engineers who want a high quality flat panel detector for their own imaging projects. for example studying the fluid movement in plants. https://www.youtube.com/watch?v=j-FHbHoiwNk (Xray timelapse by Ben Krasnow / Applied Science using a GE Flashpad) |
| − | * Veterinary practices, which can put surplus human grade detectors to good use at a fraction of the cost of new equipment. | + | *Veterinary practices, which can put surplus human grade detectors to good use at a fraction of the cost of new equipment. |
| − | * Clinics and hospitals in countries and regions where a complete commercial system is unaffordable, allowing serviceable detectors to keep providing diagnostic imaging instead of being scrapped. | + | *Clinics and hospitals in countries and regions where a complete commercial system is unaffordable, allowing serviceable detectors to keep providing diagnostic imaging instead of being scrapped. |
| − | [[File:Xray current clamp multimeter.png|thumb|xray of a current clamp multimeter]] | + | |
| + | [[File:Xray current clamp multimeter.png|thumb|xray of a current clamp multimeter using a different detector]] | ||
This is done for human good. Every detector returned to service is one less | This is done for human good. Every detector returned to service is one less | ||
piece of working medical hardware sent to landfill, and potentially one more | piece of working medical hardware sent to landfill, and potentially one more | ||
| Line 41: | Line 45: | ||
[[File:Ge flashpad tether cable pinout diagram.png|thumb|Pinout Diagram of the Tether cable]] | [[File:Ge flashpad tether cable pinout diagram.png|thumb|Pinout Diagram of the Tether cable]] | ||
[[File:Ge flashpad tether wiring for ethernet cable.jpg|thumb|Professional Tether wiring to attach a Ethernet Cable and 12V socket to it.]] | [[File:Ge flashpad tether wiring for ethernet cable.jpg|thumb|Professional Tether wiring to attach a Ethernet Cable and 12V socket to it.]] | ||
| − | The Flashpad uses a '''~40 × 40 cm CsI scintillator''' bonded to a [[Thin-film transistor|TFT]] photodetector array mounted on glass. The assembly is highly sensitive to shock and impact damage. | + | The Flashpad uses a '''~40 × 40 cm CsI scintillator''' bonded to a [[Thin-film transistor|TFT]] photodetector array mounted on glass. The assembly is highly sensitive to shock and impact damage. See section [[#Reading the Drop and Shock Event Log|drop and shock event log]] |
{| class="wikitable" | {| class="wikitable" | ||
| Line 62: | Line 66: | ||
The theoretical 5 lp/mm spatial resolution is primarily limited in practice by the focal spot size of the X-ray source. Use of an anti-scatter grid can improve effective resolution. | The theoretical 5 lp/mm spatial resolution is primarily limited in practice by the focal spot size of the X-ray source. Use of an anti-scatter grid can improve effective resolution. | ||
| − | === Status of Reverse Engineering === | + | ===Status of Reverse Engineering=== |
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
| − | ! What !! Status | + | !What!!Status |
|- | |- | ||
| − | | Connect, beacon, ACK || Works | + | |Connect, beacon, ACK||Works |
|- | |- | ||
| − | | PORT_SETUP || Works | + | |PORT_SETUP||Works |
|- | |- | ||
| − | | SIGNATURE_REQUEST / serial number readout || Works | + | |SIGNATURE_REQUEST / serial number readout||Works |
|- | |- | ||
| − | | Script download (Scripts 7, 8, 1) || Works | + | |Script download (Scripts 7, 8, 1)||Works |
|- | |- | ||
| − | | EXECUTE_SCRIPT || Works | + | |EXECUTE_SCRIPT||Works |
|- | |- | ||
| − | | EXECUTION_COMPLETE (dark acquisition) || Works | + | |EXECUTION_COMPLETE (dark acquisition)||Works but without image readout its speculation. |
|- | |- | ||
| − | | EXECUTION_COMPLETE (standard acquisition) || Needs real X-ray trigger to test | + | |EXECUTION_COMPLETE (standard acquisition)||Needs real X-ray trigger to test |
|- | |- | ||
| − | | Image data on port 6660 || Does not work yet, under investigation. Will not send Imagedata | + | |Image data on port 6660||Does not work yet, under investigation. Will not send Imagedata... |
|} | |} | ||
| Line 98: | Line 102: | ||
| − | == Communication Protocol == | + | ==Communication Protocol== |
| − | This section documents the URP/PDAP protocol used by the GE Flashpad (codename '''Apollo''') to communicate with a host over Ethernet. All findings are based on live capture tests, configuration files, and reverse-engineering of the GE SuperBee software stack. | + | This section documents the URP/PDAP protocol used by the GE Flashpad (codename '''Apollo''' (Or FeiTian, Mammo, Gryphon etc.) to communicate with a host over Ethernet. All findings are based on live capture tests, configuration files, and reverse-engineering of the GE SuperBee software stack. |
{{Note|All findings are based on a single unit and may not be fully accurate.}} | {{Note|All findings are based on a single unit and may not be fully accurate.}} | ||
| − | === Network Setup === | + | ===Network Setup=== |
All findings were over Ethernet only, no UWB or WIFI has been used. | All findings were over Ethernet only, no UWB or WIFI has been used. | ||
| Line 115: | Line 119: | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
| − | ! Role !! IP !! Port !! Direction | + | !Role!!IP!!Port!!Direction |
|- | |- | ||
| − | | All commands: host -> detector || 192.168.1.30 || '''8100''' (UDP) || Host sends here | + | |All commands: host -> detector||192.168.1.30||'''8100''' (UDP)||Host sends here |
|- | |- | ||
| − | | Discovery beacons: detector -> host || - || '''4500''' (UDP) || Detector sends here initially | + | |Discovery beacons: detector -> host||-||'''4500''' (UDP)||Detector sends here initially |
|- | |- | ||
| − | | Protocol replies: detector -> host || - || '''5550''' (UDP) || Detector sends here after setup | + | |Protocol replies: detector -> host||-||'''5550''' (UDP)||Detector sends here after setup |
|- | |- | ||
| − | | Image pixel data: detector -> host || - || '''6660''' (UDP) || Detector streams frames here | + | |Image pixel data: detector -> host||-||'''6660''' (UDP)||Detector streams frames here |
|} | |} | ||
In practice you can listen on port '''5550''' for everything (beacons and replies) by advertising that port in both <code>SYSTEM_STARTUP</code> and <code>PORT_SETUP</code>. The detector sends to whichever port was most recently configured. | In practice you can listen on port '''5550''' for everything (beacons and replies) by advertising that port in both <code>SYSTEM_STARTUP</code> and <code>PORT_SETUP</code>. The detector sends to whichever port was most recently configured. | ||
| − | === Protocol Layers === | + | ===Protocol Layers=== |
Two layers, carried over UDP: | Two layers, carried over UDP: | ||
| − | ; URP (Unified Registration Protocol) | + | ;URP (Unified Registration Protocol) |
| − | : 8-byte wrapper on every packet. Handles sequencing and acknowledgement. | + | :8-byte wrapper on every packet. Handles sequencing and acknowledgement. |
| − | ; PDAP (Proprietary Detector Access Protocol) | + | ;PDAP (Proprietary Detector Access Protocol) |
| − | : The actual command layer, present inside URP packets when <code>CmdFlag = 0</code>. | + | :The actual command layer, present inside URP packets when <code>CmdFlag = 0</code>. |
Every UDP packet starts with a URP header: | Every UDP packet starts with a URP header: | ||
| Line 144: | Line 148: | ||
</pre> | </pre> | ||
| − | * <code>CmdFlag = 0</code> -- data packet, PDAP command follows. | + | *<code>CmdFlag = 0</code> -- data packet, PDAP command follows. |
| − | * <code>CmdFlag = 1</code> -- bare ACK, no PDAP body. SeqId echoes the packet being acknowledged. | + | *<code>CmdFlag = 1</code> -- bare ACK, no PDAP body. SeqId echoes the packet being acknowledged. |
Both sides must ACK every data packet immediately. The detector silently drops packets with a SeqId it has already seen, so always increment SeqId for each new command. | Both sides must ACK every data packet immediately. The detector silently drops packets with a SeqId it has already seen, so always increment SeqId for each new command. | ||
| Line 155: | Line 159: | ||
</pre> | </pre> | ||
| − | === Connecting to the Detector === | + | ===Connecting to the Detector=== |
The connection sequence is: | The connection sequence is: | ||
| − | # Broadcast <code>SYSTEM_STARTUP</code> to tell the detector where to reply. | + | #Broadcast <code>SYSTEM_STARTUP</code> to tell the detector where to reply. |
| − | # The detector sends a <code>BEACON</code> back -- ACK it and sync your sequence counter. | + | #The detector sends a <code>BEACON</code> back -- ACK it and sync your sequence counter. |
| − | # Send <code>PORT_SETUP</code> to configure the reply and image ports. | + | #Send <code>PORT_SETUP</code> to configure the reply and image ports. |
| − | # Send <code>SIGNATURE_REQUEST</code> to read the detector identity (serial number, model, firmware, MAC). | + | #Send <code>SIGNATURE_REQUEST</code> to read the detector identity (serial number, model, firmware, MAC). |
| − | ==== Step 1: SYSTEM_STARTUP ==== | + | ====Step 1: SYSTEM_STARTUP==== |
Broadcast UDP to <code>192.168.1.255:8100</code>. Always uses <code>SeqId = 0</code>. | Broadcast UDP to <code>192.168.1.255:8100</code>. Always uses <code>SeqId = 0</code>. | ||
| Line 196: | Line 200: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | ==== Step 2: Receiving the BEACON and ACKing ==== | + | ====Step 2: Receiving the BEACON and ACKing==== |
The detector broadcasts a <code>BEACON</code> (cmd_type=1) roughly every 2 seconds. After sending SYSTEM_STARTUP you should get one quickly. Parse the detector SeqId from the URP header and ACK it: | The detector broadcasts a <code>BEACON</code> (cmd_type=1) roughly every 2 seconds. After sending SYSTEM_STARTUP you should get one quickly. Parse the detector SeqId from the URP header and ACK it: | ||
| Line 212: | Line 216: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | ==== Step 3: PORT_SETUP ==== | + | ====Step 3: PORT_SETUP==== |
Tells the detector which host ports to use for replies and image data. | Tells the detector which host ports to use for replies and image data. | ||
| Line 230: | Line 234: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | ==== Step 4: Reading the Serial Number (SIGNATURE_REQUEST) ==== | + | ====Step 4: Reading the Serial Number (SIGNATURE_REQUEST)==== |
Empty command, the detector replies with a 54-byte payload containing its identity. | Empty command, the detector replies with a 54-byte payload containing its identity. | ||
| Line 263: | Line 267: | ||
</pre> | </pre> | ||
| − | === Running an Acquisition === | + | ===Running an Acquisition=== |
After the connection sequence above, download scripts to the detector and then execute them. | After the connection sequence above, download scripts to the detector and then execute them. | ||
| − | ==== ROE Initialisation (Script 7) ==== | + | ====ROE Initialisation (Script 7)==== |
Always run this first. It initialises the readout electronics and takes about 4-5 seconds. The detector sends host event ID 17 when done. | Always run this first. It initialises the readout electronics and takes about 4-5 seconds. The detector sends host event ID 17 when done. | ||
| − | ==== Dark / Offset Acquisition (Script 1) ==== | + | ====Dark / Offset Acquisition (Script 1)==== |
No X-ray source required. Runs a dark-field exposure for offset calibration. Use this to verify the acquisition pipeline without a generator. | No X-ray source required. Runs a dark-field exposure for offset calibration. Use this to verify the acquisition pipeline without a generator. | ||
| − | ==== Standard Acquisition (Script 0) ==== | + | ====Standard Acquisition (Script 0)==== |
Requires an actual X-ray exposure. <code>EXECUTION_COMPLETE</code> will '''not''' arrive without a real X-ray trigger. | Requires an actual X-ray exposure. <code>EXECUTION_COMPLETE</code> will '''not''' arrive without a real X-ray trigger. | ||
| − | ==== Sequence Overview ==== | + | ====Sequence Overview==== |
<pre> | <pre> | ||
| Line 318: | Line 322: | ||
</pre> | </pre> | ||
| − | === Sequence Counter Rules === | + | ===Sequence Counter Rules=== |
| + | |||
| + | *<code>SYSTEM_STARTUP</code> always uses <code>SeqId = 0</code>. | ||
| + | *After the first beacon, set host SeqId to beacon_SeqId + 1. | ||
| + | *Increment SeqId by 1 for every new data packet (CmdFlag=0). | ||
| + | *Bare ACKs echo the SeqId of the packet being acknowledged and do not consume a SeqId. | ||
| + | *The detector silently drops any packet with a SeqId it has already processed, so never reuse one. | ||
| + | |||
| + | ==Script Download== | ||
| + | |||
| + | The detector does not have fixed acquisition commands. Instead the host | ||
| + | downloads small programs, called scripts, that the detector stores by ID and | ||
| + | then runs on command. A script is a sequence of primitive operations: program | ||
| + | the ROE (readout electronics) registers, wait, acquire an image, signal an | ||
| + | event. The acquisition behavior of the panel is defined entirely by these | ||
| + | downloaded scripts, which are taken from the application mode XML files (for | ||
| + | example IDC_URP_SE_2200.xml for single energy, 2048 by 2048). | ||
| + | |||
| + | ===The GENERIC_SCRIPT command=== | ||
| + | |||
| + | A script is sent to the detector with the GENERIC_SCRIPT command, cmd_type 5. | ||
| + | The wire format is: | ||
| + | |||
| + | [cmd_type : 4 LE] = 5 | ||
| + | [length : 4 LE] = 8 + sum(command sizes) + 2 | ||
| + | [script header : 8 bytes] | ||
| + | [packed command 1] | ||
| + | [packed command 2] | ||
| + | ... | ||
| + | [terminator : 2 bytes] = 00 00 | ||
| + | |||
| + | The 8 byte script header is: | ||
| + | |||
| + | {| class="wikitable" | ||
| + | !Offset!!Size!!Field!!Meaning | ||
| + | |- | ||
| + | |0||2 LE||scriptID||the slot the script is stored in | ||
| + | |- | ||
| + | |2||2 LE||repeatCount||0 = run once, 65535 = loop forever | ||
| + | |- | ||
| + | |4||4 LE||repeatEvent||event ID that breaks an infinite loop (0 if unused) | ||
| + | |} | ||
| + | |||
| + | Note: the application mode XML lists a fourth header value, ROEdata. This is a | ||
| + | substitution source for the script's commands, not a field on the wire. The wire | ||
| + | header is exactly 8 bytes. | ||
| + | |||
| + | ===Packed command primitives=== | ||
| + | |||
| + | Each command inside the script starts with a one byte DETECTORCODE that selects | ||
| + | its type and length: | ||
| + | |||
| + | {| class="wikitable" | ||
| + | !DETECTORCODE!!Command!!Size!!Layout (after the code byte) | ||
| + | |- | ||
| + | |1||Acquisition||17||typeMode:1, imageId:1, noScrubs:1, scrubDuration:4 LE, maxExposeTime:4 LE, tailTime:4 LE, transferMode:1 | ||
| + | |- | ||
| + | |2||ROE command||14||responseFlag:1, timerValue:4 LE, roeCmd:4 LE, roeData:4 LE | ||
| + | |- | ||
| + | |3||Send host event||5||eventId:4 LE | ||
| + | |- | ||
| + | |4||Wait for host event||9||eventId:4 LE, timeout:4 LE | ||
| + | |- | ||
| + | |5||Delay||5||delayMicroseconds:4 LE | ||
| + | |} | ||
| + | |||
| + | For example, a ROE command sets a readout register (roeCmd, roeData) and an | ||
| + | Acquisition command triggers the actual exposure window and image readout. | ||
| + | |||
| + | ===Download sequence=== | ||
| + | |||
| + | #The host sends the GENERIC_SCRIPT packet for a given scriptID. | ||
| + | #The detector replies with a bare acknowledgement carrying the same sequence ID. (It may also send a GENERIC_SCRIPT status reply with a status byte; status 0 means accepted.) | ||
| + | #The host repeats this for each script it wants resident on the detector. | ||
| + | #When ready, the host sends EXECUTE_SCRIPT, cmd_type 6, which runs the stored scripts. During and after execution the detector sends status notifications, and finally EXECUTION_COMPLETE, cmd_type 0x10000. | ||
| + | |||
| + | Scripts are persistent for the session: once downloaded they can be executed | ||
| + | without resending. | ||
| + | |||
| + | ===The standard script set=== | ||
| + | |||
| + | The single energy mode downloads four scripts in order. The scriptID in the | ||
| + | header is the slot; the XML refers to them by a separate DETECTOR_SCRIPT_ID. | ||
| + | |||
| + | {| class="wikitable" | ||
| + | !scriptID!!Purpose!!Notes | ||
| + | |- | ||
| + | |7||ROE initialization||Scan setup plus a sequence of zero pulses, ends by sending host event 17 | ||
| + | |- | ||
| + | |8||Standby loop||repeatCount 65535, broken by event 41; keeps the panel idle between acquisitions | ||
| + | |- | ||
| + | |0||Standard acquisition||Requires a real X-ray exposure to complete | ||
| + | |- | ||
| + | |1||Dark / offset acquisition||Completes with no X-ray; used to test the full flow | ||
| + | |} | ||
| + | |||
| + | For a no X-ray test, download script 7 then script 1 and execute. The detector | ||
| + | runs the ROE init, performs the dark acquisition, and reports | ||
| + | EXECUTION_COMPLETE in a few seconds. | ||
| − | + | ===Inspecting the encoded scripts=== | |
| − | + | See [[#Main test py script: Usage and Parameters|Main test py script]] | |
| − | + | The exact wire bytes of every built script can be printed without touching the | |
| − | + | detector: | |
| − | |||
| + | python flashpad_acquire.py --dump-scripts | ||
| + | This shows the full GENERIC_SCRIPT encoding (header, packed commands, and | ||
| + | terminator) as hex, which is useful for verifying the format against the XML. | ||
| − | == Sensor Readout and Host Registration == | + | ==Sensor Readout and Host Registration== |
This section documents the reverse engineered sensor telemetry interface and the | This section documents the reverse engineered sensor telemetry interface and the | ||
| Line 335: | Line 439: | ||
the detector at port 8100; replies return to the host command port. | the detector at port 8100; replies return to the host command port. | ||
| − | === Sensor Readout === | + | ===Sensor Readout=== |
The detector exposes an analog sensor interface (the DEM, Detector Environment | The detector exposes an analog sensor interface (the DEM, Detector Environment | ||
| Line 341: | Line 445: | ||
acquisition state. | acquisition state. | ||
| − | ==== Commands ==== | + | ====Commands==== |
{| class="wikitable" | {| class="wikitable" | ||
| − | ! cmd_type !! Meaning !! Request payload !! Reply payload | + | !cmd_type!!Meaning!!Request payload!!Reply payload |
|- | |- | ||
| − | | 0x7900 || Raw sensor read || [sensorId:4 LE] || [value:4 LE] (12 bit ADC count) | + | |0x7900||Raw sensor read||[sensorId:4 LE]||[value:4 LE] (12 bit ADC count) |
|- | |- | ||
| − | | 0x7902 || Converted sensor read || [sensorId:4 LE] || [value:4 LE] (engineering units, signed) | + | |0x7902||Converted sensor read||[sensorId:4 LE]||[value:4 LE] (engineering units, signed) |
|- | |- | ||
| − | | 0x7904 || Detailed / radio || [selector:4 LE] || [value:4 LE] | + | |0x7904||Detailed / radio||[selector:4 LE]||[value:4 LE] |
|} | |} | ||
| Line 363: | Line 467: | ||
Registration section for the read protocol). | Registration section for the read protocol). | ||
| − | ==== Power rails (live readings) ==== | + | ====Power rails (live readings)==== |
All supply rails read correctly via 0x7902 and are within normal range for a | All supply rails read correctly via 0x7902 and are within normal range for a | ||
| Line 369: | Line 473: | ||
{| class="wikitable" | {| class="wikitable" | ||
| − | ! Sensor !! sensorId !! Converted !! Value | + | !Sensor!!sensorId!!Converted!!Value |
|- | |- | ||
| − | | DCIN_RAW || 10 || 12100 mV || +12.10 V (main DC input) | + | |DCIN_RAW||10||12100 mV||+12.10 V (main DC input) |
|- | |- | ||
| − | | LCORE_UNREG || 11 || 1724 mV || +1.72 V | + | |LCORE_UNREG||11||1724 mV||+1.72 V |
|- | |- | ||
| − | | LPANA_UNREG || 12 || 5776 mV || +5.78 V | + | |LPANA_UNREG||12||5776 mV||+5.78 V |
|- | |- | ||
| − | | LNANA_UNREG || 13 || -5801 mV || -5.80 V | + | |LNANA_UNREG||13||-5801 mV||-5.80 V |
|- | |- | ||
| − | | SCAN_VCC || 14 || 5086 mV || +5.09 V (gate driver) | + | |SCAN_VCC||14||5086 mV||+5.09 V (gate driver) |
|- | |- | ||
| − | | P5V_REF || 16 || 5025 mV || +5.03 V (5 V reference) | + | |P5V_REF||16||5025 mV||+5.03 V (5 V reference) |
|- | |- | ||
| − | | V_ON || 17 || 11085 mV || +11.09 V (TFT gate on) | + | |V_ON||17||11085 mV||+11.09 V (TFT gate on) |
|- | |- | ||
| − | | V_OFF || 18 || -12378 mV || -12.38 V (TFT gate off) | + | |V_OFF||18||-12378 mV||-12.38 V (TFT gate off) |
|- | |- | ||
| − | | V_COMMON || 19 || -9385 mV || -9.39 V | + | |V_COMMON||19||-9385 mV||-9.39 V |
|- | |- | ||
| − | | 3V3 || 29 || 3177 mV || +3.18 V (3.3 V logic) | + | |3V3||29||3177 mV||+3.18 V (3.3 V logic) |
|- | |- | ||
| − | | VCC_UNREG || 37 || 3399 mV || +3.40 V | + | |VCC_UNREG||37||3399 mV||+3.40 V |
|- | |- | ||
| − | | PARCPREG || 38 || 3768 mV || +3.77 V (ARC preamp +) | + | |PARCPREG||38||3768 mV||+3.77 V (ARC preamp +) |
|- | |- | ||
| − | | NARCPREG || 39 || -3732 mV || -3.73 V (ARC preamp -) | + | |NARCPREG||39||-3732 mV||-3.73 V (ARC preamp -) |
|- | |- | ||
| − | | PANA_UNREG || 45 || 18352 mV || +18.35 V (photodiode bias +) | + | |PANA_UNREG||45||18352 mV||+18.35 V (photodiode bias +) |
|- | |- | ||
| − | | NANA_UNREG || 46 || -19251 mV || -19.25 V (photodiode bias -) | + | |NANA_UNREG||46||-19251 mV||-19.25 V (photodiode bias -) |
|} | |} | ||
| Line 405: | Line 509: | ||
P5VA_SW, N5VA_SW, FGATE_NVC_L, FGATE_PVC_L, etc.) read at or near zero. | P5VA_SW, N5VA_SW, FGATE_NVC_L, FGATE_PVC_L, etc.) read at or near zero. | ||
| − | ==== Known limitations ==== | + | ====Known limitations==== |
| − | * '''Temperatures''' (Temp_Surface, sensorId 336; Temp_Panel, sensorId 352): the raw 0x7900 read rails at 0x3FF (1023, the ADC maximum), indicating an open thermistor path while the panel is idle. This is a hardware/state condition, not a command problem, and the temperature is not readable from the host in this state. | + | *'''Temperatures''' (Temp_Surface, sensorId 336; Temp_Panel, sensorId 352): the raw 0x7900 read rails at 0x3FF (1023, the ADC maximum), indicating an open thermistor path while the panel is idle. This is a hardware/state condition, not a command problem, and the temperature is not readable from the host in this state. |
| − | * '''Unimplemented sensors''': sensorIds 70 (Accelerator), 78 (Gravity), and 256 to 259 (Battery status, name, life, capacity), and 272 (Grid status) are not implemented on this DEM. The detector returns the leftover conversion register contents (a duplicate of a previously read sensor) rather than a real value, so these rows must be discarded. | + | *'''Unimplemented sensors''': sensorIds 70 (Accelerator), 78 (Gravity), and 256 to 259 (Battery status, name, life, capacity), and 272 (Grid status) are not implemented on this DEM. The detector returns the leftover conversion register contents (a duplicate of a previously read sensor) rather than a real value, so these rows must be discarded. |
| − | * '''Accelerometer''': functional through a separate addressing scheme using raw cmd 0x7900 with selectors 0x42 (X), 0x43 (Y), 0x44 (Z), returning 12 bit per axis counts. | + | *'''Accelerometer''': functional through a separate addressing scheme using raw cmd 0x7900 with selectors 0x42 (X), 0x43 (Y), 0x44 (Z), returning 12 bit per axis counts. |
| − | === Host Registration (Pairing) === | + | ===Host Registration (Pairing)=== |
URP stands for Unified Registration Protocol. The detector maintains a host | URP stands for Unified Registration Protocol. The detector maintains a host | ||
| Line 417: | Line 521: | ||
primary host, so registration is a prerequisite for image transfer. | primary host, so registration is a prerequisite for image transfer. | ||
| − | ==== Host identity (MAC and HostId) ==== | + | ====Host identity (MAC and HostId)==== |
Each host is identified by a 16 character HostId derived deterministically from | Each host is identified by a 16 character HostId derived deterministically from | ||
| Line 423: | Line 527: | ||
script) is: | script) is: | ||
| − | # Take the eth0 MAC, remove the colon separators, convert to upper case (12 hex characters). | + | #Take the eth0 MAC, remove the colon separators, convert to upper case (12 hex characters). |
| − | # Prepend the last 4 characters to the full 12 characters. | + | #Prepend the last 4 characters to the full 12 characters. |
| − | # The result is 16 hex characters. | + | #The result is 16 hex characters. |
Example: MAC 00:6f:00:01:0a:3a becomes 006F00010A3A, then the last four (0A3A) | Example: MAC 00:6f:00:01:0a:3a becomes 006F00010A3A, then the last four (0A3A) | ||
are prepended, giving HostId 0A3A006F00010A3A. | are prepended, giving HostId 0A3A006F00010A3A. | ||
| − | ==== HostList structure ==== | + | ====HostList structure==== |
The host list is stored in flash at offset 0x940000 and can be read back over | The host list is stored in flash at offset 0x940000 and can be read back over | ||
| Line 448: | Line 552: | ||
... 4 CRC (big endian; see below) | ... 4 CRC (big endian; see below) | ||
| − | ==== HostList from the unit under test ==== | + | ====HostList from the unit under test==== |
Detector: MAC 40:F4:A0:00:78:4D, serial UA45829-7, model 5340000-7, | Detector: MAC 40:F4:A0:00:78:4D, serial UA45829-7, model 5340000-7, | ||
| Line 455: | Line 559: | ||
{| class="wikitable" | {| class="wikitable" | ||
| − | ! Index !! HostId !! Derived MAC !! Name | + | !Index!!HostId!!Derived MAC!!Name |
|- | |- | ||
| − | | 0 || 2C6400045FB42C64 || 00:04:5F:B4:2C:64 || Haus 207 / ITS | + | |0||2C6400045FB42C64||00:04:5F:B4:2C:64||Haus 207 / ITS |
|- | |- | ||
| − | | 1 || B044E8393512B044 || E8:39:35:12:B0:44 || Not Initialized | + | |1||B044E8393512B044||E8:39:35:12:B0:44||Not Initialized |
|- | |- | ||
| − | | 2 || 5CF800045FB15CF8 || 00:04:5F:B1:5C:F8 || Not_Initialized | + | |2||5CF800045FB15CF8||00:04:5F:B1:5C:F8||Not_Initialized |
|} | |} | ||
| Line 467: | Line 571: | ||
deployment, but no primary host is currently designated. | deployment, but no primary host is currently designated. | ||
| − | ==== Checksum (CRC) ==== | + | ====Checksum (CRC)==== |
The HostList (and other flash data blobs) are protected by a 4 byte trailing | The HostList (and other flash data blobs) are protected by a 4 byte trailing | ||
CRC. Parameters: | CRC. Parameters: | ||
| − | * Polynomial: 0x04C11DB7 | + | *Polynomial: 0x04C11DB7 |
| − | * Initial value: 0 | + | *Initial value: 0 |
| − | * MSB first, augmented message style (the data bit is shifted into the LSB; no input or output reflection; no final XOR) | + | *MSB first, augmented message style (the data bit is shifted into the LSB; no input or output reflection; no final XOR) |
| − | * Stored big endian | + | *Stored big endian |
To generate: compute the CRC over the data followed by four zero bytes, then | To generate: compute the CRC over the data followed by four zero bytes, then | ||
| Line 492: | Line 596: | ||
# trailer = crc(data + b"\x00\x00\x00\x00"), stored big endian | # trailer = crc(data + b"\x00\x00\x00\x00"), stored big endian | ||
| − | ==== Read and write transport ==== | + | ====Read and write transport==== |
Data blobs (including the HostList) are moved with a configure / buffer / | Data blobs (including the HostList) are moved with a configure / buffer / | ||
| Line 500: | Line 604: | ||
{| class="wikitable" | {| class="wikitable" | ||
| − | ! Step !! cmd_type !! host to detector !! detector to host | + | !Step!!cmd_type!!host to detector!!detector to host |
|- | |- | ||
| − | | Configure || 0x13 || [uploadId:4 LE] || [status:1][totalSize:4 LE] | + | |Configure||0x13||[uploadId:4 LE]||[status:1][totalSize:4 LE] |
|- | |- | ||
| − | | Buffer || 0x14 || [bufId:4 LE][numBytes:4 LE] || [bufId:4 LE][numBytes:4 LE][data] | + | |Buffer||0x14||[bufId:4 LE][numBytes:4 LE]||[bufId:4 LE][numBytes:4 LE][data] |
|} | |} | ||
| Line 510: | Line 614: | ||
{| class="wikitable" | {| class="wikitable" | ||
| − | ! Step !! cmd_type !! host to detector !! detector to host | + | !Step!!cmd_type!!host to detector!!detector to host |
|- | |- | ||
| − | | Configure || 0x0E || [downloadId:4 LE][totalSize:4 LE] || [status:1] | + | |Configure||0x0E||[downloadId:4 LE][totalSize:4 LE]||[status:1] |
|- | |- | ||
| − | | Buffer || 0x0F || [bufId:4 LE][numBytes:4 LE][data] || [status:1][reserved:4] | + | |Buffer||0x0F||[bufId:4 LE][numBytes:4 LE][data]||[status:1][reserved:4] |
|- | |- | ||
| − | | Commit || 0x10 || (empty) || [status:1] | + | |Commit||0x10||(empty)||[status:1] |
|} | |} | ||
| Line 523: | Line 627: | ||
download can corrupt flash; the operation is irreversible on this hardware. | download can corrupt flash; the operation is irreversible on this hardware. | ||
| − | ==== Registering a new host ==== | + | ====Registering a new host==== |
To register a host and make it the image destination, read the current HostList | To register a host and make it the image destination, read the current HostList | ||
| Line 531: | Line 635: | ||
(0x0E / 0x0F / 0x10). | (0x0E / 0x0F / 0x10). | ||
| − | == Reading the Drop and Shock Event Log == | + | ===Reading the Drop and Shock Event Log=== |
The detector contains an accelerometer based shock watchdog that records drop | The detector contains an accelerometer based shock watchdog that records drop | ||
| Line 539: | Line 643: | ||
in the small onboard EEPROM on the detector PCB. | in the small onboard EEPROM on the detector PCB. | ||
| − | === How to read it === | + | ====How to read it==== |
The shock log is exposed as data category 0x72 on the upload (read) interface. | The shock log is exposed as data category 0x72 on the upload (read) interface. | ||
| Line 550: | Line 654: | ||
(32 KB I2C part) for anyone reading the hardware directly. | (32 KB I2C part) for anyone reading the hardware directly. | ||
| − | === Example output === | + | ====Example output==== |
The following is the actual log read from detector serial UA45829-7. The file | The following is the actual log read from detector serial UA45829-7. The file | ||
| Line 589: | Line 693: | ||
157804584600183, 149266438700135 } | 157804584600183, 149266438700135 } | ||
| − | === Interpreting the records === | + | ====Interpreting the records==== |
The record key encodes the event time and the peak axis values (an epoch style | The record key encodes the event time and the peak axis values (an epoch style | ||
| Line 601: | Line 705: | ||
{| class="wikitable" | {| class="wikitable" | ||
| − | ! Date and time !! VibrationX !! VibrationY !! VibrationZ | + | !Date and time!!VibrationX!!VibrationY!!VibrationZ |
|- | |- | ||
| − | | 2017-04-20 06:59:47 || 0 || 0 || 135 | + | |2017-04-20 06:59:47||0||0||135 |
|- | |- | ||
| − | | 2020-01-03 11:04:06 || 0 || 0 || 183 | + | |2020-01-03 11:04:06||0||0||183 |
|- | |- | ||
| − | | 2020-06-14 07:52:04 || -172 || 0 || -113 | + | |2020-06-14 07:52:04||-172||0||-113 |
|- | |- | ||
| − | | 2020-11-13 05:40:30 || 0 || 0 || 170 | + | |2020-11-13 05:40:30||0||0||170 |
|- | |- | ||
| − | | 2020-12-17 06:11:45 || 0 || 0 || -101 | + | |2020-12-17 06:11:45||0||0||-101 |
|- | |- | ||
| − | | 2021-11-07 07:03:55 || 0 || 0 || -123 | + | |2021-11-07 07:03:55||0||0||-123 |
|- | |- | ||
| − | | 2022-01-27 06:21:30 || -132 || 0 || -119 | + | |2022-01-27 06:21:30||-132||0||-119 |
|} | |} | ||
| − | === Reading the summary fields === | + | ====Reading the summary fields==== |
{| class="wikitable" | {| class="wikitable" | ||
| − | ! Field !! Meaning | + | !Field!!Meaning |
|- | |- | ||
| − | | LastKnownGoodTime || Timestamp of the last successful self check (here 2024-04-04), the most recent point at which the detector was known to be operating normally | + | |LastKnownGoodTime||Timestamp of the last successful self check (here 2024-04-04), the most recent point at which the detector was known to be operating normally |
|- | |- | ||
| − | | NumberOfL3Events || Count of lower severity (L3) shock events; zero on this unit | + | |NumberOfL3Events||Count of lower severity (L3) shock events; zero on this unit |
|- | |- | ||
| − | | NumberOfL5Events || Count of high severity (L5) shock events; seven on this unit, matching the seven records above | + | |NumberOfL5Events||Count of high severity (L5) shock events; seven on this unit, matching the seven records above |
|- | |- | ||
| − | | ShockDataTimeList || Index of all stored event keys, newest first | + | |ShockDataTimeList||Index of all stored event keys, newest first |
|} | |} | ||
| Line 635: | Line 739: | ||
of field use and last reported a good self check in April 2024. | of field use and last reported a good self check in April 2024. | ||
| − | == FlashPad Detector: Internal Flash Dump and Data Files == | + | ==FlashPad Detector: Internal Flash Dump and Data Files== |
This section documents how the full contents of the detector's internal flash | This section documents how the full contents of the detector's internal flash | ||
| Line 642: | Line 746: | ||
serial UA45829-7, MAC 40:F4:A0:00:78:4D, firmware 1.6.0.4.2.0.1.3). | serial UA45829-7, MAC 40:F4:A0:00:78:4D, firmware 1.6.0.4.2.0.1.3). | ||
| − | === Background === | + | ===Background=== |
The detector's main storage is a Spansion/Cypress S29GL512P (marked | The detector's main storage is a Spansion/Cypress S29GL512P (marked | ||
| Line 651: | Line 755: | ||
complete, byte exact dump possible over UDP with no physical access. | complete, byte exact dump possible over UDP with no physical access. | ||
| − | === Extraction method === | + | ===Extraction method=== |
The upload interface is the read counterpart of the firmware download path and | The upload interface is the read counterpart of the firmware download path and | ||
| Line 658: | Line 762: | ||
{| class="wikitable" | {| class="wikitable" | ||
| − | ! Step !! cmd_type !! host to detector !! detector to host | + | !Step!!cmd_type!!host to detector!!detector to host |
|- | |- | ||
| − | | Configure || 0x13 || [uploadId:4 LE] || [status:1][totalSize:4 LE] | + | |Configure||0x13||[uploadId:4 LE]||[status:1][totalSize:4 LE] |
|- | |- | ||
| − | | Buffer || 0x14 || [bufId:4 LE][numBytes:4 LE] || [bufId:4 LE][numBytes:4 LE][data] | + | |Buffer||0x14||[bufId:4 LE][numBytes:4 LE]||[bufId:4 LE][numBytes:4 LE][data] |
|} | |} | ||
| Line 671: | Line 775: | ||
image transfers as 65536 chunks of 1024 bytes. | image transfers as 65536 chunks of 1024 bytes. | ||
| − | === Recovered files === | + | ===Recovered files=== |
A sweep of the unit returned 18 readable blobs: | A sweep of the unit returned 18 readable blobs: | ||
{| class="wikitable" | {| class="wikitable" | ||
| − | ! Upload ID !! Size (bytes) !! Contents | + | !Upload ID!!Size (bytes)!!Contents |
|- | |- | ||
| − | | 0xFD || 67108864 || Full 64 MB NOR flash image | + | |0xFD||67108864||Full 64 MB NOR flash image |
|- | |- | ||
| − | | 0xFF || 16777216 || 16 MB region (firmware / FPGA mirror or image buffer) | + | |0xFF||16777216||16 MB region (firmware / FPGA mirror or image buffer) |
|- | |- | ||
| − | | 0xFE || 524288 || Bootloader (512 KB; SPI loader, Nios reset code) | + | |0xFE||524288||Bootloader (512 KB; SPI loader, Nios reset code) |
|- | |- | ||
| − | | 0x10 || 6815783 || Calibration map, dose level 1 | + | |0x10||6815783||Calibration map, dose level 1 |
|- | |- | ||
| − | | 0x11 || 6815783 || Calibration map, dose level 2 | + | |0x11||6815783||Calibration map, dose level 2 |
|- | |- | ||
| − | | 0x12 || 6815783 || Calibration map, dose level 3 | + | |0x12||6815783||Calibration map, dose level 3 |
|- | |- | ||
| − | | 0x06 || 49254 || Table referencing conditioner/generator serial UA2010-8U005 | + | |0x06||49254||Table referencing conditioner/generator serial UA2010-8U005 |
|- | |- | ||
| − | | 0x50 || 19938 || Per mode calibration coefficients | + | |0x50||19938||Per mode calibration coefficients |
|- | |- | ||
| − | | 0x51 || 19582 || Per mode calibration coefficients | + | |0x51||19582||Per mode calibration coefficients |
|- | |- | ||
| − | | 0x07 || 12187 || Sensor conversion table (text; see Sensor Readout) | + | |0x07||12187||Sensor conversion table (text; see Sensor Readout) |
|- | |- | ||
| − | | 0x72 || 3001 || Shock / drop event log (text) | + | |0x72||3001||Shock / drop event log (text) |
|- | |- | ||
| − | | 0x0E || 624 || Panel geometry / configuration (binary) | + | |0x0E||624||Panel geometry / configuration (binary) |
|- | |- | ||
| − | | 0x71 || 504 || Host list / registration record (see Host Registration) | + | |0x71||504||Host list / registration record (see Host Registration) |
|- | |- | ||
| − | | 0x0F || 233 || Host to detector compatibility table (text) | + | |0x0F||233||Host to detector compatibility table (text) |
|- | |- | ||
| − | | 0x08 || 62 || Serial and model strings | + | |0x08||62||Serial and model strings |
|- | |- | ||
| − | | 0x0A || 48 || Manufacturing codes | + | |0x0A||48||Manufacturing codes |
|- | |- | ||
| − | | 0x52 || 30 || Serial string | + | |0x52||30||Serial string |
|- | |- | ||
| − | | 0x0B || 24 || Small marker / identifier | + | |0x0B||24||Small marker / identifier |
|} | |} | ||
| − | === Flash image layout (upload ID 0xFD) === | + | ===Flash image layout (upload ID 0xFD)=== |
A block scan of the 64 MB image shows the following regions: | A block scan of the 64 MB image shows the following regions: | ||
{| class="wikitable" | {| class="wikitable" | ||
| − | ! Range !! Contents | + | !Range!!Contents |
|- | |- | ||
| − | | 0x000000 to 0x800000 || Firmware and FPGA configuration | + | |0x000000 to 0x800000||Firmware and FPGA configuration |
|- | |- | ||
| − | | 0x800000 to 0x940000 || Sparse configuration area | + | |0x800000 to 0x940000||Sparse configuration area |
|- | |- | ||
| − | | 0x940000 || Host list / registration record (matches upload ID 0x71) | + | |0x940000||Host list / registration record (matches upload ID 0x71) |
|- | |- | ||
| − | | 0x1000000 to 0x2800000 || Calibration and image data (16 to 40 MB) | + | |0x1000000 to 0x2800000||Calibration and image data (16 to 40 MB) |
|- | |- | ||
| − | | 0x2800000 to 0x4000000 || Erased / unused (40 to 64 MB) | + | |0x2800000 to 0x4000000||Erased / unused (40 to 64 MB) |
|} | |} | ||
| Line 737: | Line 841: | ||
0x940000, confirming that the 504 byte 0x71 read maps to this flash region. | 0x940000, confirming that the 504 byte 0x71 read maps to this flash region. | ||
| − | === Notes on individual files === | + | ===Notes on individual files=== |
| − | * '''0x07 (sensor table)''': plain text, header DetectorSerialNumber = UA45829-7, revision 1.2. Lists every sensor by name, command (0x7902), and sensorId. Also carries panel geometry in its [Common] section: 16 bit depth, 2048 by 2048 pixels, active area from (12, 12) to (2035, 2035), pixel pitch 0.2 mm, corner radius 1416.8, panel saturation 150. | + | *'''0x07 (sensor table)''': plain text, header DetectorSerialNumber = UA45829-7, revision 1.2. Lists every sensor by name, command (0x7902), and sensorId. Also carries panel geometry in its [Common] section: 16 bit depth, 2048 by 2048 pixels, active area from (12, 12) to (2035, 2035), pixel pitch 0.2 mm, corner radius 1416.8, panel saturation 150. |
| − | * '''0x10, 0x11, 0x12 (calibration maps)''': three distinct blobs of identical size, corresponding to the low, medium, and high dose calibration sets. Each begins with the firmware version and serial (header bytes 01 06 00 04 02 00 01 03 followed by the serial). Required to apply gain and offset correction to raw images. | + | *'''0x10, 0x11, 0x12 (calibration maps)''': three distinct blobs of identical size, corresponding to the low, medium, and high dose calibration sets. Each begins with the firmware version and serial (header bytes 01 06 00 04 02 00 01 03 followed by the serial). Required to apply gain and offset correction to raw images. |
| − | * '''0xFE (bootloader)''': contains the strings spi_load.S and ../../boot and Nios II reset vector code. | + | *'''0xFE (bootloader)''': contains the strings spi_load.S and ../../boot and Nios II reset vector code. |
| − | * '''0x72 (shock log)''': text records of drop / vibration events with timestamps and per axis values; the same data is mirrored in the detector's small onboard EEPROM. | + | *'''0x72 (shock log)''': text records of drop / vibration events with timestamps and per axis values; the same data is mirrored in the detector's small onboard EEPROM. |
| − | * '''0x06''': references a different serial, UA2010-8U005, likely the conditioner or generator board rather than the panel. | + | *'''0x06''': references a different serial, UA2010-8U005, likely the conditioner or generator board rather than the panel. |
| − | === Significance === | + | ===Significance=== |
The complete flash image and all calibration data are recoverable over the | The complete flash image and all calibration data are recoverable over the | ||
| Line 754: | Line 858: | ||
| + | ==Teardown / Internal Pictures / Hardware analysis== | ||
| + | |||
| + | <gallery mode="packed" heights="200"> | ||
| + | File:GE Flashpad under the hood.jpg|the Carbon fiber sleeve is held by 9 screws and can taken off without force. | ||
| + | File:Ge flashpad powersupply pcb in handle.jpg|Power supply PCB | ||
| + | File:Ge flashpad uwb board pcb.jpg|UWB PCB for Wireless USB using a RTU7105 | ||
| + | File:Ge flashpad uwb pcb backside.jpg|Backside of UWB PCB | ||
| + | File:Ge flashpad power supply pcb backside.jpg|Backside of Power supply PCB | ||
| + | File:Ge flashpad main pcb closeup1.jpg|Main area of the PCB with its Altera Cyclone 3 FPGA | ||
| + | File:Ge flashpad main pcb closeup2.jpg|AD7892 is a 600ksps 12bit ADC, SN74LVC8T245 a 8bit bus transceiver and DG9408EDN a 8ch MUX | ||
| + | File:Ge flashpad bottom connector ethernet isolation pcb.jpg|Isolation PCB for bottom connector | ||
| + | File:Ge flashpad main pcb powersupply section.jpg|Powersupply section for FPGA and ROIC | ||
| + | File:Ge flashpad main pcb closeup 3.jpg|AD9764AR 14-Bit, 125 MSPS DAC and DS1682 integrated elapsed-time recorder | ||
| + | File:Ge flashpad main pcb closeup 4.jpg|Accelerometer is a 834-0500 500G 3 Axis unit, and TJ500AE SPDT gigabit LAN switch | ||
| + | File:Ge flashpad bottom connector.jpg|bottom connector for docking stand | ||
| + | File:Ge flashpad main pcb closeup 5.jpg|Ethernet transformers and ADM34 RS-485/RS-422 Transceiver | ||
| + | File:Ge flashpad main pcb closeup 6.jpg|Altera EPM570f100c5n CPLD | ||
| + | File:Ge flashpad main pcb closeup 7.jpg|Spansion GL512P10FF1R1 512 Mbit NOR flash IC holding everything | ||
| + | File:Ge flashpad main pcb closeup 8.jpg|24LC256I 32k EEPROM for storing the accelerometer events | ||
| + | </gallery> | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | ==Main test py script: Usage and Parameters== | ||
| + | |||
| + | current iteration found here: https://pastebin.com/vEfAresk | ||
| + | |||
| + | flashpad_acquire.py is a single file Python 3 tool that speaks the FlashPad | ||
| + | URP/PDAP protocol over UDP. It implements discovery, the control handshake, | ||
| + | script download and execution, sensor readout, full data backup, and the host | ||
| + | registration (pairing) write. It requires only the Python standard library. | ||
| + | |||
| + | ===Default network configuration=== | ||
| + | |||
| + | These defaults match the values in ConnectionPoint.cfg and can be overridden on | ||
| + | the command line. | ||
| + | |||
| + | {| class="wikitable" | ||
| + | !Setting!!Default!!Meaning | ||
| + | |- | ||
| + | |Detector IP||192.168.1.30||the panel; listens for all commands on UDP 8100 | ||
| + | |- | ||
| + | |Host IP||192.168.1.1||this machine, announced in SYSTEM_STARTUP | ||
| + | |- | ||
| + | |Detector port||8100||where commands are sent | ||
| + | |- | ||
| + | |Host command port||5550||where the detector returns protocol replies | ||
| + | |- | ||
| + | |Host image port||6660||where pixel data would stream | ||
| + | |- | ||
| + | |Discovery port||4500||where the detector sends its beacons | ||
| + | |} | ||
| + | |||
| + | ===Connection and general options=== | ||
| + | |||
| + | {| class="wikitable" | ||
| + | !Option!!Default!!Effect | ||
| + | |- | ||
| + | | --detector-ip IP||192.168.1.30||detector address | ||
| + | |- | ||
| + | | --host-ip IP||192.168.1.1||host address sent in SYSTEM_STARTUP | ||
| + | |- | ||
| + | | --output-dir DIR||.||where raw images are written | ||
| + | |- | ||
| + | | --timeout SECONDS||5.0||UDP receive timeout | ||
| + | |- | ||
| + | | --exec-timeout SECONDS||60||how long to wait for EXECUTION_COMPLETE | ||
| + | |- | ||
| + | | --skip-discovery||off||send SYSTEM_STARTUP once and proceed, skipping the discovery loop | ||
| + | |- | ||
| + | | --quiet||off||suppress the verbose per packet log | ||
| + | |} | ||
| + | |||
| + | ===Read and diagnostic modes (non destructive)=== | ||
| + | |||
| + | These only read from the detector. None of them write anything. | ||
| + | |||
| + | {| class="wikitable" | ||
| + | !Option!!What it does!!What to expect | ||
| + | |- | ||
| + | | --sensors||Reads the full 32 entry sensor table using cmd 0x7902 (converted) and 0x7900 (raw), side by side.||A decoded table of supply rails in volts. Temperatures show as railed. Unimplemented sensors (accelerometer DEM id, battery, grid) are flagged as stale. | ||
| + | |- | ||
| + | | --no-roe-init||With --sensors, skips the Script7 ROE init before reading.||Slightly faster sensor read, marginally less reliable. | ||
| + | |- | ||
| + | | --probe-data||Reads DetectorInfo, the HostList (registration state), and cal results via the upload protocol (cmd 0x13/0x14).||A hexdump and text decode of each. Shows whether any host is registered and which HostId the detector expects. | ||
| + | |- | ||
| + | | --backup [DIR]||Sweeps all upload IDs 0x00 to 0xFF and saves every readable blob to DIR/detector_backup_timestamp/.||A manifest plus one .bin per readable ID, including the full 64 MB flash image and the calibration maps. Do this before any write. Large blobs take a few minutes each. | ||
| + | |- | ||
| + | | --backup-range LO-HI||Limits the --backup sweep, for example 0x40-0xA0.||Faster, partial backup. | ||
| + | |- | ||
| + | | --dump-scripts||Prints the wire bytes of all built scripts as hex and exits.||No network activity; useful for inspecting the script encoding. | ||
| + | |- | ||
| + | | --listen||Passively listens on the host command port for 30 seconds.||Prints any packets the detector sends; useful for watching beacons. | ||
| + | |} | ||
| + | |||
| + | ===Acquisition modes=== | ||
| + | |||
| + | The default invocation (no mode flag) runs a standard acquisition, which | ||
| + | requires an actual X-ray exposure to complete. | ||
| + | |||
| + | {| class="wikitable" | ||
| + | !Option!!What it does!!What to expect | ||
| + | |- | ||
| + | |(no flag)||Standard acquisition (Script 0).||Needs an X-ray exposure to reach EXECUTION_COMPLETE. | ||
| + | |- | ||
| + | | --dark||Runs the dark/offset acquisition (Script 1) before the standard one.||Two acquisitions; the dark one needs no X-ray. | ||
| + | |- | ||
| + | | --dark-only||Runs only the dark acquisition (Script 1).||Completes in a few seconds with no X-ray. The detector acquires and holds an 8 buffer image. Best way to test the full flow. | ||
| + | |- | ||
| + | | --no-standby||Omits the Script 8 standby loop.||Use if the standby loop blocks acquisition. | ||
| + | |- | ||
| + | | --two-exec||Executes Script 7 alone, waits, then Script 1. Only with --dark-only.||Tests the two stage execution hypothesis. | ||
| + | |- | ||
| + | | --parallel||Answers the detector status query (0x30000) with the cmd 0x99 ParallelImageTransfer reply instead of cmd 9.||Experiment to see if the parallel reply triggers a pixel push. (Result so far: it does not.) | ||
| + | |- | ||
| + | | --sweep-acq||Sweeps the dark acquisition across type_mode and transfer_mode values, watching for a 0x0F pixel push.||Non destructive experiment; reports which combination, if any, makes the detector stream. | ||
| + | |- | ||
| + | | --preview||Single dark acquisition on the preview path (transfer_mode 2, 4 buffer image). Watches port 6660 for a push.||One clean connection; run a packet capture on the detector address in parallel. | ||
| + | |} | ||
| + | |||
| + | Note: at present the detector acquires and holds an image but does not push the | ||
| + | 0x0F pixel frames. The acquisition modes complete the handshake and acquisition, | ||
| + | but image streaming is still being investigated. | ||
| − | === | + | ===Registration and write operations=== |
| + | |||
| + | These can write to the detector's flash. Every write is a dry run by default and | ||
| + | only takes effect when --commit is added. Always run --backup first. | ||
| + | |||
| + | {| class="wikitable" | ||
| + | !Option!!What it does | ||
| + | |- | ||
| + | | --register-self||Adds this host to the detector HostList and sets it as the primary host, so the detector should stream images to it. Appends an entry, sets IndexToPrimaryHost, recomputes the CRC, and writes via id 0x71. Dry run unless --commit. | ||
| + | |- | ||
| + | | --host-mac MAC||The MAC of the interface talking to the detector, used to derive this host's HostId for --register-self. Default 00:6f:00:01:0a:3a. | ||
| + | |- | ||
| + | | --smoke-test-write||Reads the HostList and writes back the identical bytes to validate the write and commit path without changing anything. Dry run unless --commit. | ||
| + | |- | ||
| + | | --restore-hostlist FILE||Writes a saved HostList blob (for example detector_backup_*/upload_0x71_504.bin) back to the detector. This is the undo button. Dry run unless --commit. | ||
| + | |- | ||
| + | | --commit||Arms the actual flash write for the operations above. Without it they only print what they would do. This writes the detector's flash and is irreversible at the chip level, although the HostList region can be restored from a backup. | ||
| + | |} | ||
| + | |||
| + | ===Typical workflow=== | ||
| + | |||
| + | #Read the current state: <code>python flashpad_acquire.py --probe-data</code> | ||
| + | #Make a full backup: <code>python flashpad_acquire.py --backup</code> | ||
| + | #Check sensors and power rails: <code>python flashpad_acquire.py --sensors</code> | ||
| + | #Dry run the registration: <code>python flashpad_acquire.py --register-self --host-mac YOUR:MAC</code> | ||
| + | #When satisfied, commit it: add <code>--commit</code> | ||
| + | #If needed, undo: <code>python flashpad_acquire.py --restore-hostlist detector_backup_*/upload_0x71_504.bin --commit</code> | ||
| − | |||
| − | + | ==Known Dead Ends== | |
| − | |||
| − | + | Things that were tried and did not work or led nowhere: | |
| − | : | ||
| − | ; | + | ;0xBEEF (48879) as reply port |
| − | : | + | :This was a bug in early test scripts. The port has no meaning in the protocol. Use 5550. |
| − | ; | + | ;CmdFlag=1 as data packet flag |
| − | : | + | :Early scripts had the URP fields swapped, setting CmdFlag=1 on data packets. The detector ignores all PDAP content in these packets. Power cycle the detector if this happened, it may retain stale port state. |
| − | ; Image data | + | ;Image data on alternate ports |
| − | : | + | :Tried listening on 48879, 6660, 5550, 8100, 1050, 6661, 9999, 4444, 7000, 7001, 9001. Nothing arrived on any of them. |
| + | ;PORT_SETUP byte order (big-endian) | ||
| + | :Current implementation sends port fields as big-endian. If the detector reads them as little-endian it computes port 44565 and 1050 instead of 5550 and 6660, which would explain why no image data arrives. Not yet confirmed. | ||
| − | + | ;Image data streaming automatically after EXECUTE_SCRIPT | |
| − | + | :Frames may not push during execution at all. The detector may require an explicit IMAGE_RETRIVAL_REQUEST (cmd_type=0x41) and IMAGE_RETRIVAL (cmd_type=0x98) after the 0x30000 notification before it streams anything. Not yet tested. | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
Latest revision as of 15:09, 12 June 2026
Contents
- 1 Overview
- 2 Purpose and Motivation
- 3 Technical Specifications
- 4 Hardware Features
- 5 Communication Protocol
- 6 Script Download
- 7 Sensor Readout and Host Registration
- 8 FlashPad Detector: Internal Flash Dump and Data Files
- 9 Teardown / Internal Pictures / Hardware analysis
- 10 Main test py script: Usage and Parameters
- 11 Known Dead Ends
Overview
The GE Flashpad is a Digital Radiography image sensor from approximately 2010, originally used in the GE Optima 220AMX mobile X-ray unit. It was designed to replace analog film in radiology, dramatically reducing image acquisition time from hours to seconds.
Due to its high original cost and specialized application, used units occasionally appear on professional B2B marketplaces in the $15,000–$50,000 range. On eBay, prices typically fall between $1,500 and $5,000, though units at this price point are often in poor condition and may fail the built-in self-test or not function at all.
All files used and mentioned are found on the official Optima XR220amx Application Software and Linux OS DVD 5406106-10 Rev3, which i cannot provide due to being copyrighted content.
Purpose and Motivation
This project documents the reverse engineering of the GE FlashPad wireless digital radiography detector used with the Optima XR200/220 AMX mobile X-ray system, with the goal of freeing these detectors for independent use.
Large numbers of these detectors reach the used market, but each one is bound to its original Optima 220 AMX console. Once separated from that console, or once the console is decommissioned, the detector is effectively useless even though the hardware is fully functional. There is a Windows software but it is not obtainable. The aim of this work is to remove that artificial barrier so that a standalone FlashPad can be paired, configured, and read out by any host, without the half million dollar console it was sold with.
The intended beneficiaries are:
- Hobbyists, researchers, and engineers who want a high quality flat panel detector for their own imaging projects. for example studying the fluid movement in plants. https://www.youtube.com/watch?v=j-FHbHoiwNk (Xray timelapse by Ben Krasnow / Applied Science using a GE Flashpad)
- Veterinary practices, which can put surplus human grade detectors to good use at a fraction of the cost of new equipment.
- Clinics and hospitals in countries and regions where a complete commercial system is unaffordable, allowing serviceable detectors to keep providing diagnostic imaging instead of being scrapped.
This is done for human good. Every detector returned to service is one less piece of working medical hardware sent to landfill, and potentially one more place that can offer X-ray imaging where it otherwise could not.
All files, findings, protocol documentation, and tools produced by this project are public and open source, so that anyone can study, reproduce, and build on the work.
Technical Specifications
The Flashpad uses a ~40 × 40 cm CsI scintillator bonded to a TFT photodetector array mounted on glass. The assembly is highly sensitive to shock and impact damage. See section drop and shock event log
| Parameter | Value |
|---|---|
| Resolution | 2048 × 2048 px |
| Bit depth | 16-bit per pixel |
| Spatial resolution | Up to 5 lp/mm (theoretical) |
| Scintillator material | Caesium iodide (CsI) |
| Detector type | TFT photodetector array (glass substrate) |
| Panel size | ~40 × 40 cm |
The theoretical 5 lp/mm spatial resolution is primarily limited in practice by the focal spot size of the X-ray source. Use of an anti-scatter grid can improve effective resolution.
Status of Reverse Engineering
| What | Status |
|---|---|
| Connect, beacon, ACK | Works |
| PORT_SETUP | Works |
| SIGNATURE_REQUEST / serial number readout | Works |
| Script download (Scripts 7, 8, 1) | Works |
| EXECUTE_SCRIPT | Works |
| EXECUTION_COMPLETE (dark acquisition) | Works but without image readout its speculation. |
| EXECUTION_COMPLETE (standard acquisition) | Needs real X-ray trigger to test |
| Image data on port 6660 | Does not work yet, under investigation. Will not send Imagedata... |
Hardware Features
Shock logging
The unit contains an internal accelerometer that logs significant shock events — but only when a battery is inserted. As there is no backup battery, shock events occurring while unpowered are not recorded.
Wireless connectivity
Some units include a UWB transmitter for Wireless USB; others may be equipped with a Wi-Fi module instead. The detector can also be operated over the tethered connection alone.
Ethernet interface
There is a 100MBit Ethernet interface trough the Tether Cable which can be tapped and used for Communication. Exposed metal contacts on the bottom connector are isolated via relays by default. Enabling Gigabit Ethernet connectivity requires shorting or driving two specific pins. This has not been investigated further at this time.
Communication Protocol
This section documents the URP/PDAP protocol used by the GE Flashpad (codename Apollo (Or FeiTian, Mammo, Gryphon etc.) to communicate with a host over Ethernet. All findings are based on live capture tests, configuration files, and reverse-engineering of the GE SuperBee software stack.
Network Setup
All findings were over Ethernet only, no UWB or WIFI has been used.
Default detector IP is 192.168.1.30. Set your host to a static IP in the same subnet, 192.168.1.1 works fine.
At first by sending random bytes over Python it answered to port 48879 (0xBEEF), which seems to be either the default port or set by Python. After SYSTEM_SETUP it got set to 5550.
| Role | IP | Port | Direction |
|---|---|---|---|
| All commands: host -> detector | 192.168.1.30 | 8100 (UDP) | Host sends here |
| Discovery beacons: detector -> host | - | 4500 (UDP) | Detector sends here initially |
| Protocol replies: detector -> host | - | 5550 (UDP) | Detector sends here after setup |
| Image pixel data: detector -> host | - | 6660 (UDP) | Detector streams frames here |
In practice you can listen on port 5550 for everything (beacons and replies) by advertising that port in both SYSTEM_STARTUP and PORT_SETUP. The detector sends to whichever port was most recently configured.
Protocol Layers
Two layers, carried over UDP:
- URP (Unified Registration Protocol)
- 8-byte wrapper on every packet. Handles sequencing and acknowledgement.
- PDAP (Proprietary Detector Access Protocol)
- The actual command layer, present inside URP packets when
CmdFlag = 0.
Every UDP packet starts with a URP header:
[SeqId : 4 bytes LE] [CmdFlag : 4 bytes LE]
CmdFlag = 0-- data packet, PDAP command follows.CmdFlag = 1-- bare ACK, no PDAP body. SeqId echoes the packet being acknowledged.
Both sides must ACK every data packet immediately. The detector silently drops packets with a SeqId it has already seen, so always increment SeqId for each new command.
When CmdFlag is 0, the PDAP header follows immediately:
[cmd_type : 4 bytes LE] [payload_len : 4 bytes LE] [payload ...]
Connecting to the Detector
The connection sequence is:
- Broadcast
SYSTEM_STARTUPto tell the detector where to reply. - The detector sends a
BEACONback -- ACK it and sync your sequence counter. - Send
PORT_SETUPto configure the reply and image ports. - Send
SIGNATURE_REQUESTto read the detector identity (serial number, model, firmware, MAC).
Step 1: SYSTEM_STARTUP
Broadcast UDP to 192.168.1.255:8100. Always uses SeqId = 0.
URP : [00 00 00 00] SeqId = 0
[00 00 00 00] CmdFlag = 0
PDAP: [01 00 00 00] cmd_type = 1
[06 00 00 00] payload_len = 6
[15 AE] host reply port = 5550 (big-endian)
[C0 A8 01 01] host IP = 192.168.1.1 (network byte order)
import socket, struct
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.bind(("0.0.0.0", 5550))
HOST_IP = "192.168.1.1"
DET_IP = "192.168.1.30"
HOST_PORT = 5550
IMG_PORT = 6660
def make_urp_packet(seq_id, pdap_bytes):
return struct.pack("<II", seq_id, 0) + pdap_bytes
pdap = struct.pack("<II", 1, 6) + struct.pack(">H", HOST_PORT) + socket.inet_aton(HOST_IP)
sock.sendto(make_urp_packet(0, pdap), ("192.168.1.255", 8100))
Step 2: Receiving the BEACON and ACKing
The detector broadcasts a BEACON (cmd_type=1) roughly every 2 seconds. After sending SYSTEM_STARTUP you should get one quickly. Parse the detector SeqId from the URP header and ACK it:
data, addr = sock.recvfrom(4096)
det_seq = struct.unpack_from("<I", data, 0)[0]
# send bare ACK
ack = struct.pack("<II", det_seq, 1)
sock.sendto(ack, (DET_IP, 8100))
# all subsequent commands start from here
host_seq = det_seq + 1
Step 3: PORT_SETUP
Tells the detector which host ports to use for replies and image data.
PDAP: [02 00 00 00] cmd_type = 2
[04 00 00 00] payload_len = 4
[15 AE] host cmd port = 5550 (big-endian)
[1A 04] host image port = 6660 (big-endian)
pdap = struct.pack("<II", 2, 4) + struct.pack(">HH", HOST_PORT, IMG_PORT)
sock.sendto(make_urp_packet(host_seq, pdap), (DET_IP, 8100))
host_seq += 1
# expect bare ACK from detector
Step 4: Reading the Serial Number (SIGNATURE_REQUEST)
Empty command, the detector replies with a 54-byte payload containing its identity.
pdap = struct.pack("<II", 3, 0) # cmd_type=3, payload_len=0
sock.sendto(make_urp_packet(host_seq, pdap), (DET_IP, 8100))
host_seq += 1
# reply is 70 bytes total: 8 URP + 8 PDAP header + 54 payload
data, _ = sock.recvfrom(4096)
payload = data[16:] # skip URP (8) + PDAP header (8)
mac = payload[0:6]
serial = payload[22:34].rstrip(b'\x00 ').decode()
model = payload[34:46].rstrip(b'\x00 ').decode()
fw_bytes = payload[46:54]
firmware = ".".join(str(b) for b in fw_bytes)
print(f"MAC: {':'.join(f'{b:02X}' for b in mac)}")
print(f"Serial: {serial}")
print(f"Model: {model}")
print(f"Firmware: {firmware}")
Expected output:
MAC: 40:F4:A0:00:78:4D Serial: UA45829-7 Model: 5340000-7 Firmware: 1.6.0.4.2.0.1.3
Running an Acquisition
After the connection sequence above, download scripts to the detector and then execute them.
ROE Initialisation (Script 7)
Always run this first. It initialises the readout electronics and takes about 4-5 seconds. The detector sends host event ID 17 when done.
Dark / Offset Acquisition (Script 1)
No X-ray source required. Runs a dark-field exposure for offset calibration. Use this to verify the acquisition pipeline without a generator.
Standard Acquisition (Script 0)
Requires an actual X-ray exposure. EXECUTION_COMPLETE will not arrive without a real X-ray trigger.
Sequence Overview
HOST DETECTOR | | |-- SYSTEM_STARTUP (broadcast) ----------->| |<- bare ACK -------------------------------| |<- BEACON (cmd_type=1) --------------------| |-- bare ACK ------------------------------>| | | |-- PORT_SETUP (cmd_type=2) -------------->| |<- bare ACK -------------------------------| | | |-- SIGNATURE_REQUEST (cmd_type=3) ------->| |<- SIGNATURE_REPLY (54 bytes) -------------| |-- bare ACK ------------------------------>| | | |-- GENERIC_SCRIPT Script7 (ROE init) ---->| |<- bare ACK or SCRIPT_DOWNLOAD_REPLY ------| | | |-- GENERIC_SCRIPT Script1 (dark acq) ---->| |<- bare ACK -------------------------------| | | |-- EXECUTE_SCRIPT (cmd_type=6) ---------->| |<- bare ACK -------------------------------| |<- EXECUTE_SCRIPT_REPLY (~230ms) ----------| |-- bare ACK ------------------------------>| |<- DETECTOR_STATE_NOTIFY (~890ms) ---------| state_id=17 |-- bare ACK ------------------------------>| | | |<- EXECUTION_COMPLETE (0x10000) -----------| |-- bare ACK ------------------------------>| | | |<- IMAGE_XFER_STATUS_QUERY (0x30000) ------| |-- bare ACK ------------------------------>| |-- IMAGE_XFER_STATUS_REPLY (cmd_type=9) ->| numMissed=0 |<- bare ACK -------------------------------|
Sequence Counter Rules
SYSTEM_STARTUPalways usesSeqId = 0.- After the first beacon, set host SeqId to beacon_SeqId + 1.
- Increment SeqId by 1 for every new data packet (CmdFlag=0).
- Bare ACKs echo the SeqId of the packet being acknowledged and do not consume a SeqId.
- The detector silently drops any packet with a SeqId it has already processed, so never reuse one.
Script Download
The detector does not have fixed acquisition commands. Instead the host downloads small programs, called scripts, that the detector stores by ID and then runs on command. A script is a sequence of primitive operations: program the ROE (readout electronics) registers, wait, acquire an image, signal an event. The acquisition behavior of the panel is defined entirely by these downloaded scripts, which are taken from the application mode XML files (for example IDC_URP_SE_2200.xml for single energy, 2048 by 2048).
The GENERIC_SCRIPT command
A script is sent to the detector with the GENERIC_SCRIPT command, cmd_type 5. The wire format is:
[cmd_type : 4 LE] = 5 [length : 4 LE] = 8 + sum(command sizes) + 2 [script header : 8 bytes] [packed command 1] [packed command 2] ... [terminator : 2 bytes] = 00 00
The 8 byte script header is:
| Offset | Size | Field | Meaning |
|---|---|---|---|
| 0 | 2 LE | scriptID | the slot the script is stored in |
| 2 | 2 LE | repeatCount | 0 = run once, 65535 = loop forever |
| 4 | 4 LE | repeatEvent | event ID that breaks an infinite loop (0 if unused) |
Note: the application mode XML lists a fourth header value, ROEdata. This is a substitution source for the script's commands, not a field on the wire. The wire header is exactly 8 bytes.
Packed command primitives
Each command inside the script starts with a one byte DETECTORCODE that selects its type and length:
| DETECTORCODE | Command | Size | Layout (after the code byte) |
|---|---|---|---|
| 1 | Acquisition | 17 | typeMode:1, imageId:1, noScrubs:1, scrubDuration:4 LE, maxExposeTime:4 LE, tailTime:4 LE, transferMode:1 |
| 2 | ROE command | 14 | responseFlag:1, timerValue:4 LE, roeCmd:4 LE, roeData:4 LE |
| 3 | Send host event | 5 | eventId:4 LE |
| 4 | Wait for host event | 9 | eventId:4 LE, timeout:4 LE |
| 5 | Delay | 5 | delayMicroseconds:4 LE |
For example, a ROE command sets a readout register (roeCmd, roeData) and an Acquisition command triggers the actual exposure window and image readout.
Download sequence
- The host sends the GENERIC_SCRIPT packet for a given scriptID.
- The detector replies with a bare acknowledgement carrying the same sequence ID. (It may also send a GENERIC_SCRIPT status reply with a status byte; status 0 means accepted.)
- The host repeats this for each script it wants resident on the detector.
- When ready, the host sends EXECUTE_SCRIPT, cmd_type 6, which runs the stored scripts. During and after execution the detector sends status notifications, and finally EXECUTION_COMPLETE, cmd_type 0x10000.
Scripts are persistent for the session: once downloaded they can be executed without resending.
The standard script set
The single energy mode downloads four scripts in order. The scriptID in the header is the slot; the XML refers to them by a separate DETECTOR_SCRIPT_ID.
| scriptID | Purpose | Notes |
|---|---|---|
| 7 | ROE initialization | Scan setup plus a sequence of zero pulses, ends by sending host event 17 |
| 8 | Standby loop | repeatCount 65535, broken by event 41; keeps the panel idle between acquisitions |
| 0 | Standard acquisition | Requires a real X-ray exposure to complete |
| 1 | Dark / offset acquisition | Completes with no X-ray; used to test the full flow |
For a no X-ray test, download script 7 then script 1 and execute. The detector runs the ROE init, performs the dark acquisition, and reports EXECUTION_COMPLETE in a few seconds.
Inspecting the encoded scripts
See Main test py script The exact wire bytes of every built script can be printed without touching the detector:
python flashpad_acquire.py --dump-scripts
This shows the full GENERIC_SCRIPT encoding (header, packed commands, and terminator) as hex, which is useful for verifying the format against the XML.
Sensor Readout and Host Registration
This section documents the reverse engineered sensor telemetry interface and the host registration (pairing) mechanism of the GE Optima XR200/220 AMX FlashPad (URP detector, SuperBee, FW 1.6.0.4.2.0.1.3). All commands are PDAP over UDP to the detector at port 8100; replies return to the host command port.
Sensor Readout
The detector exposes an analog sensor interface (the DEM, Detector Environment Monitor) that is independent of the image transfer path and works regardless of acquisition state.
Commands
| cmd_type | Meaning | Request payload | Reply payload |
|---|---|---|---|
| 0x7900 | Raw sensor read | [sensorId:4 LE] | [value:4 LE] (12 bit ADC count) |
| 0x7902 | Converted sensor read | [sensorId:4 LE] | [value:4 LE] (engineering units, signed) |
| 0x7904 | Detailed / radio | [selector:4 LE] | [value:4 LE] |
Note: cmd 0x7902 IS supported on this firmware and returns calibrated engineering units (millivolts for the supply rails, signed; 0.1 degree C for temperatures). Earlier documentation that marked 0x7902 as unsupported is incorrect. The host side conversion coefficients are not required; the detector performs the conversion internally.
The full sensor map (name, sensorId) was recovered from the detector's own [Sensor] configuration table, read back over the upload interface (see the Registration section for the read protocol).
Power rails (live readings)
All supply rails read correctly via 0x7902 and are within normal range for a flat panel detector. Representative readings:
| Sensor | sensorId | Converted | Value |
|---|---|---|---|
| DCIN_RAW | 10 | 12100 mV | +12.10 V (main DC input) |
| LCORE_UNREG | 11 | 1724 mV | +1.72 V |
| LPANA_UNREG | 12 | 5776 mV | +5.78 V |
| LNANA_UNREG | 13 | -5801 mV | -5.80 V |
| SCAN_VCC | 14 | 5086 mV | +5.09 V (gate driver) |
| P5V_REF | 16 | 5025 mV | +5.03 V (5 V reference) |
| V_ON | 17 | 11085 mV | +11.09 V (TFT gate on) |
| V_OFF | 18 | -12378 mV | -12.38 V (TFT gate off) |
| V_COMMON | 19 | -9385 mV | -9.39 V |
| 3V3 | 29 | 3177 mV | +3.18 V (3.3 V logic) |
| VCC_UNREG | 37 | 3399 mV | +3.40 V |
| PARCPREG | 38 | 3768 mV | +3.77 V (ARC preamp +) |
| NARCPREG | 39 | -3732 mV | -3.73 V (ARC preamp -) |
| PANA_UNREG | 45 | 18352 mV | +18.35 V (photodiode bias +) |
| NANA_UNREG | 46 | -19251 mV | -19.25 V (photodiode bias -) |
Switched rails that are inactive while the panel is idle (PARCVA_U, P5VA_SW, N5VA_SW, FGATE_NVC_L, FGATE_PVC_L, etc.) read at or near zero.
Known limitations
- Temperatures (Temp_Surface, sensorId 336; Temp_Panel, sensorId 352): the raw 0x7900 read rails at 0x3FF (1023, the ADC maximum), indicating an open thermistor path while the panel is idle. This is a hardware/state condition, not a command problem, and the temperature is not readable from the host in this state.
- Unimplemented sensors: sensorIds 70 (Accelerator), 78 (Gravity), and 256 to 259 (Battery status, name, life, capacity), and 272 (Grid status) are not implemented on this DEM. The detector returns the leftover conversion register contents (a duplicate of a previously read sensor) rather than a real value, so these rows must be discarded.
- Accelerometer: functional through a separate addressing scheme using raw cmd 0x7900 with selectors 0x42 (X), 0x43 (Y), 0x44 (Z), returning 12 bit per axis counts.
Host Registration (Pairing)
URP stands for Unified Registration Protocol. The detector maintains a host list in its internal NOR flash. Image data is delivered only to a registered primary host, so registration is a prerequisite for image transfer.
Host identity (MAC and HostId)
Each host is identified by a 16 character HostId derived deterministically from the host's eth0 MAC address. The derivation (from the vendor generateHostId script) is:
- Take the eth0 MAC, remove the colon separators, convert to upper case (12 hex characters).
- Prepend the last 4 characters to the full 12 characters.
- The result is 16 hex characters.
Example: MAC 00:6f:00:01:0a:3a becomes 006F00010A3A, then the last four (0A3A) are prepended, giving HostId 0A3A006F00010A3A.
HostList structure
The host list is stored in flash at offset 0x940000 and can be read back over the upload interface as data category 0x71. Layout:
Offset Size Field
0x00 16 DetectorDeviceId (ASCII)
0x10 16 ConnectionSecretKey (ASCII; "XXXXXXXXXXXXXXXX" when unset)
0x20 16 DetectorName (ASCII)
0x30 16 DetectorCode (ASCII)
0x40 2 CurrentNumberOfHosts (uint16 LE)
0x42 2 IndexToPrimaryHost (uint16 LE; 0xFFFF = none)
0x44 144*N Host entries, 144 bytes each:
+0x00 16 HostId (ASCII)
+0x10 .. host name / department string
+0x50 .. location string
... 4 CRC (big endian; see below)
HostList from the unit under test
Detector: MAC 40:F4:A0:00:78:4D, serial UA45829-7, model 5340000-7, firmware 1.6.0.4.2.0.1.3. DetectorName starShape_green. ConnectionSecretKey unset. CurrentNumberOfHosts = 3, IndexToPrimaryHost = 0xFFFF (no primary host).
| Index | HostId | Derived MAC | Name |
|---|---|---|---|
| 0 | 2C6400045FB42C64 | 00:04:5F:B4:2C:64 | Haus 207 / ITS |
| 1 | B044E8393512B044 | E8:39:35:12:B0:44 | Not Initialized |
| 2 | 5CF800045FB15CF8 | 00:04:5F:B1:5C:F8 | Not_Initialized |
The unit is therefore not factory fresh; it carries registrations from a prior deployment, but no primary host is currently designated.
Checksum (CRC)
The HostList (and other flash data blobs) are protected by a 4 byte trailing CRC. Parameters:
- Polynomial: 0x04C11DB7
- Initial value: 0
- MSB first, augmented message style (the data bit is shifted into the LSB; no input or output reflection; no final XOR)
- Stored big endian
To generate: compute the CRC over the data followed by four zero bytes, then append the result big endian. Verification: the CRC over (data plus stored CRC) equals zero.
def crc(data, poly=0x04C11DB7, init=0):
c = init
for byte in data:
for bit in (0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01):
msb = c & 0x80000000
c = (c << 1) & 0xFFFFFFFF
if byte & bit: c |= 1
if msb: c ^= poly
return c
# trailer = crc(data + b"\x00\x00\x00\x00"), stored big endian
Read and write transport
Data blobs (including the HostList) are moved with a configure / buffer / finalize sequence.
Read (upload, detector to host), non destructive:
| Step | cmd_type | host to detector | detector to host |
|---|---|---|---|
| Configure | 0x13 | [uploadId:4 LE] | [status:1][totalSize:4 LE] |
| Buffer | 0x14 | [bufId:4 LE][numBytes:4 LE] | [bufId:4 LE][numBytes:4 LE][data] |
Write (download, host to detector), persistent (writes flash):
| Step | cmd_type | host to detector | detector to host |
|---|---|---|---|
| Configure | 0x0E | [downloadId:4 LE][totalSize:4 LE] | [status:1] |
| Buffer | 0x0F | [bufId:4 LE][numBytes:4 LE][data] | [status:1][reserved:4] |
| Commit | 0x10 | (empty) | [status:1] |
bufId is a zero based chunk index. The HostList uses id 0x71 for both read and write. The commit command (0x10) reuses the firmware flash path, so a malformed download can corrupt flash; the operation is irreversible on this hardware.
Registering a new host
To register a host and make it the image destination, read the current HostList (0x71), append a 144 byte entry carrying the new HostId, increment CurrentNumberOfHosts, set IndexToPrimaryHost to the new entry's index, recompute the trailing CRC, and write the blob back via the download sequence (0x0E / 0x0F / 0x10).
Reading the Drop and Shock Event Log
The detector contains an accelerometer based shock watchdog that records drop and impact events into non volatile memory. This log is useful when evaluating a used unit, since it reveals how many serious shocks the panel has survived and when. The same data is held both in the main flash (read over the network) and in the small onboard EEPROM on the detector PCB.
How to read it
The shock log is exposed as data category 0x72 on the upload (read) interface. It is retrieved with the standard non destructive configure and buffer sequence (cmd 0x13 to configure, cmd 0x14 to pull the chunks). On the unit under test the blob is 3001 bytes of plain text in the GE .dyn configuration format, so no decoding is required beyond reading it as ASCII.
The same records are mirrored in binary form in the onboard 24LC256 EEPROM (32 KB I2C part) for anyone reading the hardware directly.
Example output
The following is the actual log read from detector serial UA45829-7. The file has two parts: a list of individual events (DetectorShockEvents) and a summary block (DetectorShockData). Each event carries a record key, a severity, a timestamp, and the peak acceleration on each axis. Two representative records:
<DetectorShockEvents>
[149266438700135]
Severity.Type = String
Severity.Val = Serious
TimeStamp.Type = String
TimeStamp.Val = 2017-4-20,6:59:47
ValidTime.Type = Number
ValidTime.Val = 0
VibrationX.Type = Number
VibrationX.Val = 0
VibrationY.Type = Number
VibrationY.Val = 0
VibrationZ.Type = Number
VibrationZ.Val = 135
[1592113924-1720-113]
Severity.Val = Serious
TimeStamp.Val = 2020-6-14,7:52:4
VibrationX.Val = -172
VibrationY.Val = 0
VibrationZ.Val = -113
The summary block at the end lists the totals and an index of all event keys:
[DetectorShockData]
LastKnownGoodTime.Val = 2024-4-4,6:53:59
NumberOfL3Events.Val = 0
NumberOfL5Events.Val = 7
ShockDataTimeList.Val = { 1643260890-1320-119, 163626503500-123,
160818190500-101, 160524243000170, 1592113924-1720-113,
157804584600183, 149266438700135 }
Interpreting the records
The record key encodes the event time and the peak axis values (an epoch style timestamp followed by the concatenated vibration readings), so it is unique per event. The VibrationX, VibrationY, and VibrationZ values are signed peak acceleration counts on each axis. Severity is reported as Serious for all logged events on this unit, which corresponds to the L5 (highest) severity class counted in the summary.
The seven recorded events for this detector:
| Date and time | VibrationX | VibrationY | VibrationZ |
|---|---|---|---|
| 2017-04-20 06:59:47 | 0 | 0 | 135 |
| 2020-01-03 11:04:06 | 0 | 0 | 183 |
| 2020-06-14 07:52:04 | -172 | 0 | -113 |
| 2020-11-13 05:40:30 | 0 | 0 | 170 |
| 2020-12-17 06:11:45 | 0 | 0 | -101 |
| 2021-11-07 07:03:55 | 0 | 0 | -123 |
| 2022-01-27 06:21:30 | -132 | 0 | -119 |
Reading the summary fields
| Field | Meaning |
|---|---|
| LastKnownGoodTime | Timestamp of the last successful self check (here 2024-04-04), the most recent point at which the detector was known to be operating normally |
| NumberOfL3Events | Count of lower severity (L3) shock events; zero on this unit |
| NumberOfL5Events | Count of high severity (L5) shock events; seven on this unit, matching the seven records above |
| ShockDataTimeList | Index of all stored event keys, newest first |
On this example the panel logged seven serious impacts over roughly five years of field use and last reported a good self check in April 2024.
FlashPad Detector: Internal Flash Dump and Data Files
This section documents how the full contents of the detector's internal flash were extracted over the network and what each recovered file contains. The detector under test is a GE Optima XR200/220 AMX FlashPad (URP detector, serial UA45829-7, MAC 40:F4:A0:00:78:4D, firmware 1.6.0.4.2.0.1.3).
Background
The detector's main storage is a Spansion/Cypress S29GL512P (marked GL512P10FFCR2), a 512 Mbit (64 MB) parallel NOR flash. A direct chip read requires bus access or desoldering and a programmer. However, the detector firmware exposes its stored data blobs, including a full image of the flash, through the read (upload) side of the data transport protocol. This makes a complete, byte exact dump possible over UDP with no physical access.
Extraction method
The upload interface is the read counterpart of the firmware download path and is non destructive (it only reports and returns data; nothing is written). Each data category is identified by an 8 bit upload ID:
| Step | cmd_type | host to detector | detector to host |
|---|---|---|---|
| Configure | 0x13 | [uploadId:4 LE] | [status:1][totalSize:4 LE] |
| Buffer | 0x14 | [bufId:4 LE][numBytes:4 LE] | [bufId:4 LE][numBytes:4 LE][data] |
A configure request returns status 0 and a total size for a valid ID, or a non zero status for an unsupported ID. The dump tool sweeps all IDs from 0x00 to 0xFF, and for every readable ID it pulls the full blob in fixed size chunks (bufId is a zero based chunk index) and writes it to a file. The 64 MB flash image transfers as 65536 chunks of 1024 bytes.
Recovered files
A sweep of the unit returned 18 readable blobs:
| Upload ID | Size (bytes) | Contents |
|---|---|---|
| 0xFD | 67108864 | Full 64 MB NOR flash image |
| 0xFF | 16777216 | 16 MB region (firmware / FPGA mirror or image buffer) |
| 0xFE | 524288 | Bootloader (512 KB; SPI loader, Nios reset code) |
| 0x10 | 6815783 | Calibration map, dose level 1 |
| 0x11 | 6815783 | Calibration map, dose level 2 |
| 0x12 | 6815783 | Calibration map, dose level 3 |
| 0x06 | 49254 | Table referencing conditioner/generator serial UA2010-8U005 |
| 0x50 | 19938 | Per mode calibration coefficients |
| 0x51 | 19582 | Per mode calibration coefficients |
| 0x07 | 12187 | Sensor conversion table (text; see Sensor Readout) |
| 0x72 | 3001 | Shock / drop event log (text) |
| 0x0E | 624 | Panel geometry / configuration (binary) |
| 0x71 | 504 | Host list / registration record (see Host Registration) |
| 0x0F | 233 | Host to detector compatibility table (text) |
| 0x08 | 62 | Serial and model strings |
| 0x0A | 48 | Manufacturing codes |
| 0x52 | 30 | Serial string |
| 0x0B | 24 | Small marker / identifier |
Flash image layout (upload ID 0xFD)
A block scan of the 64 MB image shows the following regions:
| Range | Contents |
|---|---|
| 0x000000 to 0x800000 | Firmware and FPGA configuration |
| 0x800000 to 0x940000 | Sparse configuration area |
| 0x940000 | Host list / registration record (matches upload ID 0x71) |
| 0x1000000 to 0x2800000 | Calibration and image data (16 to 40 MB) |
| 0x2800000 to 0x4000000 | Erased / unused (40 to 64 MB) |
The host list strings (the DetectorName starShape_green, the registered host name Haus 207 / ITS, and the unset ConnectionSecretKey placeholder) appear at 0x940000, confirming that the 504 byte 0x71 read maps to this flash region.
Notes on individual files
- 0x07 (sensor table): plain text, header DetectorSerialNumber = UA45829-7, revision 1.2. Lists every sensor by name, command (0x7902), and sensorId. Also carries panel geometry in its [Common] section: 16 bit depth, 2048 by 2048 pixels, active area from (12, 12) to (2035, 2035), pixel pitch 0.2 mm, corner radius 1416.8, panel saturation 150.
- 0x10, 0x11, 0x12 (calibration maps): three distinct blobs of identical size, corresponding to the low, medium, and high dose calibration sets. Each begins with the firmware version and serial (header bytes 01 06 00 04 02 00 01 03 followed by the serial). Required to apply gain and offset correction to raw images.
- 0xFE (bootloader): contains the strings spi_load.S and ../../boot and Nios II reset vector code.
- 0x72 (shock log): text records of drop / vibration events with timestamps and per axis values; the same data is mirrored in the detector's small onboard EEPROM.
- 0x06: references a different serial, UA2010-8U005, likely the conditioner or generator board rather than the panel.
Significance
The complete flash image and all calibration data are recoverable over the network without opening the detector, providing a full byte exact backup of the unit before any write operation. The calibration maps are needed for image correction, and the firmware image (the live, deployed build) is more complete than the partial firmware files shipped on the recovery media.
Teardown / Internal Pictures / Hardware analysis
Main test py script: Usage and Parameters
current iteration found here: https://pastebin.com/vEfAresk
flashpad_acquire.py is a single file Python 3 tool that speaks the FlashPad URP/PDAP protocol over UDP. It implements discovery, the control handshake, script download and execution, sensor readout, full data backup, and the host registration (pairing) write. It requires only the Python standard library.
Default network configuration
These defaults match the values in ConnectionPoint.cfg and can be overridden on the command line.
| Setting | Default | Meaning |
|---|---|---|
| Detector IP | 192.168.1.30 | the panel; listens for all commands on UDP 8100 |
| Host IP | 192.168.1.1 | this machine, announced in SYSTEM_STARTUP |
| Detector port | 8100 | where commands are sent |
| Host command port | 5550 | where the detector returns protocol replies |
| Host image port | 6660 | where pixel data would stream |
| Discovery port | 4500 | where the detector sends its beacons |
Connection and general options
| Option | Default | Effect |
|---|---|---|
| --detector-ip IP | 192.168.1.30 | detector address |
| --host-ip IP | 192.168.1.1 | host address sent in SYSTEM_STARTUP |
| --output-dir DIR | . | where raw images are written |
| --timeout SECONDS | 5.0 | UDP receive timeout |
| --exec-timeout SECONDS | 60 | how long to wait for EXECUTION_COMPLETE |
| --skip-discovery | off | send SYSTEM_STARTUP once and proceed, skipping the discovery loop |
| --quiet | off | suppress the verbose per packet log |
Read and diagnostic modes (non destructive)
These only read from the detector. None of them write anything.
| Option | What it does | What to expect |
|---|---|---|
| --sensors | Reads the full 32 entry sensor table using cmd 0x7902 (converted) and 0x7900 (raw), side by side. | A decoded table of supply rails in volts. Temperatures show as railed. Unimplemented sensors (accelerometer DEM id, battery, grid) are flagged as stale. |
| --no-roe-init | With --sensors, skips the Script7 ROE init before reading. | Slightly faster sensor read, marginally less reliable. |
| --probe-data | Reads DetectorInfo, the HostList (registration state), and cal results via the upload protocol (cmd 0x13/0x14). | A hexdump and text decode of each. Shows whether any host is registered and which HostId the detector expects. |
| --backup [DIR] | Sweeps all upload IDs 0x00 to 0xFF and saves every readable blob to DIR/detector_backup_timestamp/. | A manifest plus one .bin per readable ID, including the full 64 MB flash image and the calibration maps. Do this before any write. Large blobs take a few minutes each. |
| --backup-range LO-HI | Limits the --backup sweep, for example 0x40-0xA0. | Faster, partial backup. |
| --dump-scripts | Prints the wire bytes of all built scripts as hex and exits. | No network activity; useful for inspecting the script encoding. |
| --listen | Passively listens on the host command port for 30 seconds. | Prints any packets the detector sends; useful for watching beacons. |
Acquisition modes
The default invocation (no mode flag) runs a standard acquisition, which requires an actual X-ray exposure to complete.
| Option | What it does | What to expect |
|---|---|---|
| (no flag) | Standard acquisition (Script 0). | Needs an X-ray exposure to reach EXECUTION_COMPLETE. |
| --dark | Runs the dark/offset acquisition (Script 1) before the standard one. | Two acquisitions; the dark one needs no X-ray. |
| --dark-only | Runs only the dark acquisition (Script 1). | Completes in a few seconds with no X-ray. The detector acquires and holds an 8 buffer image. Best way to test the full flow. |
| --no-standby | Omits the Script 8 standby loop. | Use if the standby loop blocks acquisition. |
| --two-exec | Executes Script 7 alone, waits, then Script 1. Only with --dark-only. | Tests the two stage execution hypothesis. |
| --parallel | Answers the detector status query (0x30000) with the cmd 0x99 ParallelImageTransfer reply instead of cmd 9. | Experiment to see if the parallel reply triggers a pixel push. (Result so far: it does not.) |
| --sweep-acq | Sweeps the dark acquisition across type_mode and transfer_mode values, watching for a 0x0F pixel push. | Non destructive experiment; reports which combination, if any, makes the detector stream. |
| --preview | Single dark acquisition on the preview path (transfer_mode 2, 4 buffer image). Watches port 6660 for a push. | One clean connection; run a packet capture on the detector address in parallel. |
Note: at present the detector acquires and holds an image but does not push the 0x0F pixel frames. The acquisition modes complete the handshake and acquisition, but image streaming is still being investigated.
Registration and write operations
These can write to the detector's flash. Every write is a dry run by default and only takes effect when --commit is added. Always run --backup first.
| Option | What it does |
|---|---|
| --register-self | Adds this host to the detector HostList and sets it as the primary host, so the detector should stream images to it. Appends an entry, sets IndexToPrimaryHost, recomputes the CRC, and writes via id 0x71. Dry run unless --commit. |
| --host-mac MAC | The MAC of the interface talking to the detector, used to derive this host's HostId for --register-self. Default 00:6f:00:01:0a:3a. |
| --smoke-test-write | Reads the HostList and writes back the identical bytes to validate the write and commit path without changing anything. Dry run unless --commit. |
| --restore-hostlist FILE | Writes a saved HostList blob (for example detector_backup_*/upload_0x71_504.bin) back to the detector. This is the undo button. Dry run unless --commit. |
| --commit | Arms the actual flash write for the operations above. Without it they only print what they would do. This writes the detector's flash and is irreversible at the chip level, although the HostList region can be restored from a backup. |
Typical workflow
- Read the current state:
python flashpad_acquire.py --probe-data - Make a full backup:
python flashpad_acquire.py --backup - Check sensors and power rails:
python flashpad_acquire.py --sensors - Dry run the registration:
python flashpad_acquire.py --register-self --host-mac YOUR:MAC - When satisfied, commit it: add
--commit - If needed, undo:
python flashpad_acquire.py --restore-hostlist detector_backup_*/upload_0x71_504.bin --commit
Known Dead Ends
Things that were tried and did not work or led nowhere:
- 0xBEEF (48879) as reply port
- This was a bug in early test scripts. The port has no meaning in the protocol. Use 5550.
- CmdFlag=1 as data packet flag
- Early scripts had the URP fields swapped, setting CmdFlag=1 on data packets. The detector ignores all PDAP content in these packets. Power cycle the detector if this happened, it may retain stale port state.
- Image data on alternate ports
- Tried listening on 48879, 6660, 5550, 8100, 1050, 6661, 9999, 4444, 7000, 7001, 9001. Nothing arrived on any of them.
- PORT_SETUP byte order (big-endian)
- Current implementation sends port fields as big-endian. If the detector reads them as little-endian it computes port 44565 and 1050 instead of 5550 and 6660, which would explain why no image data arrives. Not yet confirmed.
- Image data streaming automatically after EXECUTE_SCRIPT
- Frames may not push during execution at all. The detector may require an explicit IMAGE_RETRIVAL_REQUEST (cmd_type=0x41) and IMAGE_RETRIVAL (cmd_type=0x98) after the 0x30000 notification before it streams anything. Not yet tested.