How do I decode M307 Temperature Guard data in Ignition?

Daniel Price9 min read
HMI / SCADAOther ManufacturerTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

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:

  1. The Ignition gateway opens a TCP client socket.
  2. The request crosses the plant Ethernet to the M307 listening port. The reference reader uses 10001.
  3. The M307 answers with a 60-byte record.
  4. The generic TCP driver frames those bytes into a message tag.
  5. 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:

  1. Mask each byte with 0xFF.
  2. Compute value = (MSB << 8) | LSB.
  3. If value >= 0x8000, subtract 0x10000.

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.

  1. Confirm link and addressing. Ping the M307 from the Ignition gateway host itself, not from an engineering laptop on a different VLAN.
  2. Confirm the TCP port is open from the gateway host:
    • Windows:
    • Linux:
    If the connection is refused, read the listening port from the SDK guide or the unit's network settings. 10001 is the reference reader's default, not a guaranteed device setting.
  3. 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.
  4. 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. A ValueError on record size or command bytes points to framing, not decoding.

How do I poll and decode the record in an Ignition gateway script?

  1. Create memory tags under a folder, for example [default]M307/. For each of Temp1, Temp2, and Internal, add Value (Float), Status (String), TimeOut (Integer), and OutOfLimits (Boolean). Add Humidity/Value, Humidity/Status, Unit, and CommOK. These paths are examples; keep them consistent with the script.
  2. 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.
  3. Set that project as the gateway scripting project. Add a Gateway Timer Event that calls m307.poll().
  4. 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 & 0xFF masks mirror the SDK's Java example. In Jython, bytearray elements 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/u16 helpers.

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?

  1. Compare each Value tag against the M307's own local reading at the same moment. Check sign, decimal position, and unit.
  2. Warm or cool one probe. Confirm that only its tag moves and that the change appears within one poll period.
  3. Disconnect a temperature probe at the terminals. Its Status should change to no_sensor or open_circuit, and Value should go null. Reconnect it and confirm it returns to ok.
  4. Drive a sensor past its configured limit. Confirm OutOfLimits goes true, and false again once the reading is back inside the limit.
  5. If the unit allows switching between C and F, change the setting. Confirm Unit follows byte 60 and the values rescale.
  6. Unplug the M307 Ethernet cable. Confirm CommOK goes false within one poll plus the 5 s timeout and that a M307 poll failed warning appears in the gateway log. Reconnect the cable and confirm CommOK returns 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.

Back to blog