The Quick Client shows Good because Ignition's generic TCP driver reports a healthy connection. It does not decode the payload. The M307 returns a 60-byte binary status record. Every temperature, humidity, alarm, resolution, and unit field sits at a fixed byte offset inside that record, and the generic driver has no map of those offsets. To get usable values, send the documented status-read command from a script, receive exactly 60 bytes, decode the record, and write the results to tags.
Why does the Quick Client show only "Good" for the M307?
A status read passes through these points:
- The Ignition gateway opens a TCP client socket.
- The request crosses the plant Ethernet to the M307 listening port. The reference reader uses
10001. - The M307 answers with a 60-byte record.
- The generic TCP driver frames those bytes into a message tag.
- The OPC server publishes that tag to the Quick Client.
Useful data stops at step 4. The driver treats the payload as a message string, and the record is binary. It starts with 0x3F 0xCD 0xDC 0x00, holds signed 16-bit big-endian values, and uses bytes such as 0x00 and values above that do not render as text. The Quick Client therefore shows the tag quality (Good) next to a value that is empty or unreadable.
The M307 read is also a request/response exchange. The client must send the status-read command each time it wants a fresh record:
- Bytes 1-4:
0x3F 0xCD 0xDC 0x00 - Bytes 5-60: 56 bytes of
0x00
A driver that only frames incoming bytes cannot issue that command on a schedule. It also cannot turn the reply into engineering units. For those two reasons, change the approach instead of tuning the driver.
What is inside the 60-byte status record?
Table 1 of the SDK integration guide (SDK-M307-M308-M309, revision 1-7) is the authoritative map. The layout the decoder relies on is below. Byte numbers are 1-based as the guide writes them; indices are 0-based as code uses them.
| Bytes | Index | Field | Encoding | Decode rule |
|---|---|---|---|---|
| 1-4 | 0-3 | Command echo | 0x3F 0xCD 0xDC 0x00 |
Reject the record if these bytes do not match |
| 5-6 | 4-5 | Temperature sensor 1 value | Signed 16-bit, MSB first | Divide by the resolution divisor |
| 7-8 | 6-7 | Temperature sensor 1 time-out | Unsigned 16-bit, MSB first | Units and meaning per the SDK guide |
| 9 | 8 | Temperature sensor 1 out-of-limits | 1 byte |
1 = out of limits |
| 10-14 | 9-13 | Temperature sensor 2 | Same 5-byte layout as sensor 1 | Same rules |
| 15-19 | 14-18 | Internal temperature sensor | Same 5-byte layout | Same rules |
| 20-24 | 19-23 | Internal humidity sensor | Value is signed 16-bit at bytes 20-21 | Always 0.1 %RH resolution: divide by 10. Confirm the time-out and flag bytes against Table 1 |
| 25-58 | 24-57 | Remaining fields | Per Table 1 | Map from the SDK guide before exposing them as tags |
| 59 | 58 | Temperature resolution | 1 byte |
10 = 0.1 degree; any other value = 1 degree |
| 60 | 59 | Temperature unit | ASCII |
0x43 = C, 0x46 = F |
The device reports sensor faults as reserved raw values in the measurement field:
| Raw value | Hex (16-bit) | Field | Meaning |
|---|---|---|---|
1000 |
Temperature | No sensor connected | |
999 |
Temperature | Open circuit | |
-999 |
Temperature | Short circuit | |
999 |
Humidity | Sensor failed |
Signed conversion follows the Java example in section 1.3 of the SDK guide:
- Mask each byte with
0xFF. - Compute
value = (MSB << 8) | LSB. - If
value >= 0x8000, subtract0x10000.
Worked example: gives 65336, which becomes -200 after the subtraction. At 0.1-degree resolution that is -20.0.
One ambiguity needs a decision. The reference reader compares the sentinels against the raw integer before scaling. At 0.1-degree resolution, a raw 999 could otherwise read as a legitimate 99.9 degrees. Check the SDK guide to confirm that sentinels are raw counts in both resolution modes before relying on that ordering.
Which integration path fits: TCP driver, gateway script, external script, or custom module?
Four paths can move this data. They differ on whether they can send the command, decode binary fields, and land values in Ignition tags.
| Approach | Sends status-read command | Decodes binary fields | Values in Ignition tags | Effort | Where it fits |
|---|---|---|---|---|---|
| Generic TCP driver | No scheduled request/response | No; payload treated as a message | Raw message only | Already configured | Not suitable for the M307 record |
| Gateway timer script (Jython) | Yes, every poll | Yes, in script | Yes, through tag writes | Moderate | Recommended for one unit or a few |
| Standalone Python 2.7 script (external to Ignition) | Yes | Yes; outputs JSON | No, unless something else ingests the JSON | Low once written | Bench test of communications and decoding |
| Custom Ignition module (Java, Ignition SDK) | Yes | Yes, in the driver | Native device and tag structure | High | Fleet deployments or when native driver behavior is required |
Recommendation: use the gateway timer script, and prove it first with the standalone reader.
- Ignition scripting runs Jython 2.7. The reference reader targets Python 2.7, not 3.x, so its decoding logic moves into the gateway almost unchanged.
- The custom module is the cleaner long-term architecture. It is a Java project against the Ignition SDK and costs far more to build and maintain.
- A working script that already decodes the record is the best specification for that module, so it is not wasted effort if you build the module later.
How do I prove the network path to the M307 before writing gateway code?
Check the path bottom-up. A script failure on a dead link looks the same as a decoding bug.
- Confirm link and addressing. Ping the M307 from the Ignition gateway host itself, not from an engineering laptop on a different VLAN.
- Confirm the TCP port is open from the gateway host:
- Windows:
- Linux:
10001is the reference reader's default, not a guaranteed device setting. - Disable or delete the generic TCP driver device in the Ignition gateway configuration. Embedded TCP servers commonly service a limited number of client sessions. A driver holding the socket can cause refused or stalled script connections.
- Run the standalone reader with Python 2.7 from a host on the same network:
python read_status_record.py 192.168.1.100 10001, replacing the example IP. A good read prints parsed JSON. AValueErroron record size or command bytes points to framing, not decoding.
How do I poll and decode the record in an Ignition gateway script?
- Create memory tags under a folder, for example
[default]M307/. For each ofTemp1,Temp2, andInternal, addValue(Float),Status(String),TimeOut(Integer), andOutOfLimits(Boolean). AddHumidity/Value,Humidity/Status,Unit, andCommOK. These paths are examples; keep them consistent with the script. - Put the functions in a project library script named
m307. Library scope avoids the variable-scoping surprises that functions defined inside event scripts can hit. - Set that project as the gateway scripting project. Add a Gateway Timer Event that calls
m307.poll(). - Set the timer to fixed-delay mode. Make the period longer than the 5 s socket timeout so an unreachable unit cannot stack overlapping executions.
Design choices in the script:
- One connection per poll. Opening and closing the socket each cycle keeps every read aligned to a fresh request. A leftover partial record from a previous cycle cannot shift the offsets.
-
Byte masking. The
& 0xFFmasks mirror the SDK's Java example. In Jython,bytearrayelements are already 0-255, so the masks are harmless there. They matter if you port the logic to Java for a custom module, because Java bytes are signed. -
Unmapped bytes. Fields in bytes 25-58 are not decoded. Add them from Table 1 using the same
s16/u16helpers.
What do bad M307 reads look like, and what causes them?
| Symptom | Cause | Correction |
|---|---|---|
Quick Client shows Good but no usable value |
Generic TCP driver passes the binary record through as a message | Move to the scripted poll; disable the driver device |
bad command echo in the gateway log |
Misaligned read, stale bytes, or wrong command frame | Keep one connection per poll; confirm the 4-byte command plus 56 zero bytes (60 total) |
closed after N of 60 bytes or a socket timeout after 5 s |
Another client holds the session, the port is wrong, or the unit is unreachable | Recheck the port test from the gateway host; remove competing connections |
| Temperatures off by a factor of 10 | Resolution byte 59 ignored | Apply the divisor: 10 when byte 59 = 10, otherwise 1 |
| Below-zero temperatures read near 6500 or higher | Unsigned conversion used on a signed field | Subtract 0x10000 when the value is at or above 0x8000
|
Status = no_sensor, open_circuit, or short_circuit
|
Raw value 1000, 999, or -999 from probe wiring |
Inspect the probe and terminals at the M307 |
Humidity sensor_failed
|
Raw humidity 999
|
Service the internal humidity sensor |
Unit = unknown
|
Byte 60 is neither 0x43 nor 0x46; the record is misaligned or not a status record |
Treat the whole record as invalid; check framing |
| Gateway timer executions pile up | Period shorter than the socket timeout in fixed-rate mode | Use fixed delay and a period above 5 s |
How do I verify the decoded values against the M307 itself?
- Compare each
Valuetag against the M307's own local reading at the same moment. Check sign, decimal position, and unit. - Warm or cool one probe. Confirm that only its tag moves and that the change appears within one poll period.
- Disconnect a temperature probe at the terminals. Its
Statusshould change tono_sensororopen_circuit, andValueshould go null. Reconnect it and confirm it returns took. - Drive a sensor past its configured limit. Confirm
OutOfLimitsgoes true, and false again once the reading is back inside the limit. - If the unit allows switching between C and F, change the setting. Confirm
Unitfollows byte 60 and the values rescale. - Unplug the M307 Ethernet cable. Confirm
CommOKgoes false within one poll plus the 5 s timeout and that aM307 poll failedwarning appears in the gateway log. Reconnect the cable and confirmCommOKreturns true with fresh timestamps on every value tag.
FAQ
How do I read a binary TCP response like the M307 status record in Ignition?
Use a gateway timer script that opens a socket and sends the command 0x3F 0xCD 0xDC 0x00 followed by 56 zero bytes. Loop on recv until all 60 bytes arrive, decode by fixed offset, and write the results to memory tags with system.tag.writeBlocking.
How do I convert two M307 bytes into a signed temperature?
Compute (MSB << 8) | LSB, subtract 0x10000 if the result is at or above 0x8000, then divide by 10 when byte 59 equals 10 (otherwise by 1). Check the raw result for 1000, 999, and -999 first; these mean no sensor, open circuit, and short circuit.
How do I get M307 data into Ignition without a dedicated driver?
The generic TCP driver cannot issue the status-read request or decode the binary fields. Use a Jython gateway script, which shares Python 2.7 syntax with the reference reader. Alternatively, build a custom Ignition module in Java based on the SDK guide's section 1.3 conversion example.