# Quality Bad\_OutOfService on DNP3 BO reads (newer driver)

**URL:** <https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650>\
**Category:** Ignition\
**Tags:** ignition81\
**Created:** [November 15, 2024, 4:17am UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650 "2024-11-15T04:17:55Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![msteele](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/msteele/32/77889_2.png) [@msteele](https://forum.inductiveautomation.com/u/msteele)\
**Post date:** [November 15, 2024, 4:17am UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/1 "2024-11-15T04:17:55Z")

</div>

Anyone familiar with this quality code for the newer DNP3 driver? `Bad("Bad_OutOfService: The source of the data is not operational.")`

I'm seeing it on all the binary outs I'm trying to read from a SEL RTAC and have no idea why - but there's nothing about this code in the driver doc.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [November 15, 2024, 1:16pm UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/2 "2024-11-15T13:16:31Z")

</div>

It means the flags are indicating the point is Offline (Online bit in flags not set).

---

<div class="post-metadata">

**Author:** ![msteele](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/msteele/32/77889_2.png) [@msteele](https://forum.inductiveautomation.com/u/msteele)\
**Post date:** [November 15, 2024, 2:39pm UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/3 "2024-11-15T14:39:32Z")

</div>

Ah, was it Bad\_Offline with the legacy driver? I was probably overthinking it but there was just a different name.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [November 15, 2024, 2:55pm UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/4 "2024-11-15T14:55:27Z")

</div>

Might have just been a generic "Bad" in the legacy driver.

---

<div class="post-metadata">

**Author:** ![msteele](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/msteele/32/77889_2.png) [@msteele](https://forum.inductiveautomation.com/u/msteele)\
**Post date:** [November 15, 2024, 3:17pm UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/5 "2024-11-15T15:17:46Z")

</div>

Is there not a way to sent trip bits and close bits on the same device on this driver?

We're not understanding the relationship between BO device settings `Trip Close Code` and `Op Type` for two reasons:

1. These settings are at a device level, implying we cannot use different bits on the same tag or on different tags on the same device?
2. LatchOn and LatchOff are still pulses under the hood, so not understanding what Latch mode would actually do.

We are wanting to have the flexibility to pulse any of the following bits, and per tag:

- Trip
- Close
- LatchOn
- LatchOff
- Pulse

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [November 15, 2024, 3:18pm UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/6 "2024-11-15T15:18:59Z")

</div>

If you need different per point settings you need to use the scripting functions instead.

> **[system.dnp.directOperateBinary | Ignition User Manual](https://docs.inductiveautomation.com/docs/8.1/appendix/scripting-functions/system-dnp/system-dnp-directOperateBinary)**
>
> Performs a Direct Operate command on a binary point.

edit: take care with the namespaces on these functions, `system.dnp3` is used by the legacy driver, `system.dnp` by the new one.

---

<div class="post-metadata">

**Author:** ![msteele](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/msteele/32/77889_2.png) [@msteele](https://forum.inductiveautomation.com/u/msteele)\
**Post date:** [November 15, 2024, 3:38pm UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/7 "2024-11-15T15:38:57Z")

</div>

I can understand DNP3 writes may be tricky since there's not one way to simply write a value to a tag. The directOperateBinary looks like it will solve that problem, but introduces another problem for deriving the device name and index rather than accepting `tagPath`.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [November 15, 2024, 3:39pm UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/8 "2024-11-15T15:39:55Z")

</div>

Yeah, you'll have to figure that out. There's zero correlation between a tag path and whatever the point index might be (unless you create one by convention).

Direct- or Select-And- Operate in DNP3 is a complex operation, it's not simply a write. There's some fidelity loss when you map it to something conceptually simpler like the OPC UA write service. The operations require many parameters and there's no way to provide those via the write service of another protocol, which is why there are some defaults to make it work.

The proper way to map DNP3 operations to OPC UA would be via OPC UA methods, and that's what we would have done if we were a more generic/standalone server, but since the primary consumer is Ignition itself we provide the scripting functions instead, which are basically the same thing but easier to use than OPC UA methods.

---

<div class="post-metadata">

**Author:** ![Morgan](https://avatars.discourse-cdn.com/v4/letter/m/0ea827/32.png) [@Morgan](https://forum.inductiveautomation.com/u/Morgan)\
**Post date:** [December 5, 2024, 9:49pm UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/9 "2024-12-05T21:49:17Z")

</div>

@msteele Hi, I'm having the exact same issue with BinaryOutput, AnalogueOutput DNP3 tags having Bad("Bad\_OutOfService: The source of the data is not operational.") quality. And I'm also reading them from a SEL RTAC using the new DNP3 driver.

Just wondering what did you do at the end to get them working please?

Below is the RTAC BO tags configuration

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/9/2/92789599e81521744ea3574097b19a0e1944514f.jpeg)

---

<div class="post-metadata">

**Author:** ![msteele](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/msteele/32/77889_2.png) [@msteele](https://forum.inductiveautomation.com/u/msteele)\
**Post date:** [December 12, 2024, 3:07am UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/10 "2024-12-12T03:07:18Z")

</div>

In our tag processor, we have two line items for each tag.

To make the data readable and get rid of the error you mention, we map the `.status` of BOs in our Ignition\_DNP tag map.

E.g. destination tag `Ignition_DNP.BO_TripBreaker_OurBreaker` as datatype SPS  
to source expression `OurRelay_SEL.OurBreaker_52a_closed` as datatype SPS.

We also have to map the trip signal from Ignition to the appropriate trip signal for a breaker or control.

E.g. destination tag `OurRelay_SEL.RB1_TripBreaker_OurBreaker.operPulse` as datatype OPERSPC to source expression `Ignition_DNP.BO_TripBreaker_OurBreaker.operTrip` as datatype OPERSPC.

---

<div class="post-metadata">

**Author:** ![msteele](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/msteele/32/77889_2.png) [@msteele](https://forum.inductiveautomation.com/u/msteele)\
**Post date:** [December 12, 2024, 6:02pm UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/11 "2024-12-12T18:02:45Z")

</div>

We finally got around to testing [`system.dnp.directOperateBinary()`](https://docs.inductiveautomation.com/docs/8.1/appendix/scripting-functions/system-dnp3/system-dnp3-directOperateBinary) to a SEL RTAC's `DNPC` 61131 data structure. Our interpretation of DNP3 is that there are 5 function codes (Trip, Close, Pulse, Latch On, Latch Off) so the point of this exercise was to map out which combination of `tcc` and `opType` produce these five function codes over the wire.

Observations:

1. \*As expected, tcc = 0, opType = 0 gives an error since there's no applicable function code.
2. †tcc = 0, opType = 2 gives an Ignition error, which makes sense because PULSE\_OFF is, per my understanding, not an actual DNP function code.
3. ‡Where tcc != 0 NUL, tcc overrides opType.
4. Additional arguments used in our test: count = 1, onTime = 1, offTime = 1. Our understanding is the time arguments should have no effect on RTAC response.
5. As mentioned upthread, if you want to poll a tag value via your BO, you need to map something to the `.status` SPS object in your RTAC. This object is a sibling to the five `.oper*` objects representing the DNP3 function codes.

Results:

| Trip Close Code `tcc` | Operation Type `opType` | RTAC Bit Received | DNP Function Code |
| --- | --- | --- | --- |
| 0=NUL | 0=NUL | \*Ignition error | |
| 0=NUL | 1=PULSE\_ON | .operPulse | Pulse |
| 0=NUL | 2=PULSE\_OFF | †Ignition error | |
| 0=NUL | 3=LATCH\_ON | .operLatchOn | Latch On |
| 0=NUL | 4=LATCH\_OFF | .operLatchOff | Latch Off |
| 1=CLOSE | 0=NUL | .operClose | Close |
| 1=CLOSE | 1=PULSE\_ON | ‡.operClose | Close |
| 1=CLOSE | 2=PULSE\_OFF | ‡.operClose | Close |
| 1=CLOSE | 3=LATCH\_ON | ‡.operClose | Close |
| 1=CLOSE | 4=LATCH\_OFF | ‡.operClose | Close |
| 2=TRIP | 0=NUL | .operTrip | Trip |
| 2=TRIP | 1=PULSE\_ON | ‡.operTrip | Trip |
| 2=TRIP | 2=PULSE\_OFF | ‡.operTrip | Trip |
| 2=TRIP | 3=LATCH\_ON | ‡.operTrip | Trip |
| 2=TRIP | 4=LATCH\_OFF | ‡.operTrip | Trip |

---

<div class="post-metadata">

**Author:** ![msteele](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/msteele/32/77889_2.png) [@msteele](https://forum.inductiveautomation.com/u/msteele)\
**Post date:** [December 13, 2024, 4:26pm UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/12 "2024-12-13T16:26:22Z")

</div>

Some helper functions for DNP3 BO and AO writes.  
`getDNP3Index` presumes your addressing uses the standard syntax, e.g. `BinaryOutput0`

```python
# DNP3 Control + conversion of tagPath to inputs needed for system.dnp.* functions

# Compile the patterns once, because "dramatically faster"
devNamePattern = re.compile(r'\[(.*?)\]')
devIndexPattern = re.compile(r'(\d+)$')

def getDnp3Device(tagPath):
	"""Extracts the device name for the given tag path."""
	
	# Since opcItemPath is a tag property, no timeout is needed.
	opcPath = system.tag.readBlocking(tagPath + '.OpcItemPath')[0].value
	
	match = devNamePattern.search(opcPath)
	if match:
		return match.group(1)
	
	return None
#

def getDnp3Index(tagPath):
	"""Extracts the DNP3 index from the """
	opcPath = system.tag.readBlocking(tagPath + '.OpcItemPath')[0].value
	
	match = devIndexPattern.search(opcPath)
	if match:
		return match.group(1)
	
	return None
#

# Map integers representing DNP3 BO commands to tcc, opType
funcCodesMap = {
	0: (2, 0), # Trip
	1: (1, 0), # Close
	2: (0, 1), # Pulse
	3: (0, 3), # Latch On
	4: (0, 4), # Latch Off
}

# Binary operate.
def doWriteDnp3Bo(tagPath, dnp3FuncCode):
	"""Wrapper for `system.dnp.directOperateBinary` func call for new DNP3 devices.
	
	dnp3FuncCodes:
		0=Trip
		1=Close
		2=Pulse
		3=Latch On
		4=Latch Off
	"""
	deviceName = control.getDnp3Device(tagPath)
	index = int(control.getDnp3Index(tagPath))
	
	tcc, opType = funcCodesMap[dnp3FuncCode]
	
	# system.dnp.directOperateBinary(deviceName, index, tcc, opType, count, onTime, offTime)
	system.dnp.directOperateBinary(deviceName, index, tcc, opType, 1, 1, 1)
#
	
# Analog operate. Default variation is float.
def doWriteDnp3Ao(tagPath, value, variation=3):
	"""Wrapper for `system.dnp.directOperateAnalog` func call for new DNP3 devices. Default variation is float."""
	deviceName = control.getDnp3Device(tagPath)
	index = int(control.getDnp3Index(tagPath))
	
	system.dnp.directOperateAnalog(deviceName, variation, index, value)
#

```

Edit: two minor bugfixes. Also, apparently `Pulse Off` may be a thing in the DNP3 standard after all, even though it's not supported by RTAC.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [February 6, 2025, 1:38am UTC](https://forum.inductiveautomation.com/t/quality-bad-outofservice-on-dnp3-bo-reads-newer-driver/98650/13 "2025-02-06T01:38:13Z")

</div>

> [@msteele](#):
>
> Also, apparently `Pulse Off` may be a thing in the DNP3 standard after all, even though it's not supported by RTAC.

It's defined as an Op Type, but not seemingly required, and this note follows later in the spec:

> Vendors are free to implement operations using the PULSE\_OFF value in the Op Type field, but these may not be interoperable with all DNP3 masters, and alternate outstation vendors may not implement these operations.
> 
> One use would be the generation of a pulse train that ends in the output remaining active. For example, if the output value was active prior to a PULSE\_OFF command, then the output would go inactive for the time determined by the Off-time and then the output would change to active.
