C1D2 sensor data parsing

I am trying to parse data from the Wireless 2-Channel Vibration Temperature Sensor V3 C1D2 Certified for Hazardous Environments using Python.

I am referring to NCDEnterprise-Python and testing with EnterpriseNCD-Example.py.

I am trying to modify ncd_enterprise.py and added the following code:
“”"
try:
if(parsed[‘sensor_type_id’] == 80):
parsed[‘sensor_type_string’] = ‘One Channel Vibration Plus’
parsed[‘sensor_data’] = type80(payload, parsed, source_address)
elif parsed[‘sensor_type_id’] == 181:
# Add specific handling for sensor type 181
parsed[‘sensor_type_string’] = ‘C1D2 Two Channel Vibration Plus’
# need type181
parsed[‘sensor_data’] = type80(payload, parsed, source_address)
#parsed[‘sensor_data’] = type181(payload[8:])
“”"
The results I got are as follows:
nodeId 0
firmware 24
battery 3.2844
battery_percent 97.7536945812808%
counter 1
sensor_type_id 181
source_address 0013A200423C9F24
sensor_type_string Unsupported Sensor
sensor_data bytearray(b’\x00\x18\x03\xfc\x01\x00\xb5\x00FLY\x00\x00\xff\xff\x00\x0c\x0c\x14\x14\x01\x00\x00\t\t\x02\x01\x0f\x03\x00\x00\x00\x7f’)

nodeId 0
firmware 24
battery 3.2844
battery_percent 97.7536945812808%
counter 1
sensor_type_id 181
source_address 0013A200423C9F24
sensor_type_string Unsupported Sensor
sensor_data bytearray(b’\x00\x18\x03\xfc\x01\x00\xb5\x00FLY\x00\x00\xff\xff\x00\x0c\x0c\x14\x14\x01\x00\x00\t\t\x02\x01\x0f\x03\x00\x00\x00\x7f’)

nodeId 0
firmware 24
battery 3.2844
battery_percent 97.7536945812808%
counter 1
sensor_type_id 181
source_address 0013A200423C9F24
sensor_type_string C1D2 Two Channel Vibration Plus
sensor_data {‘error’: ‘Error found, Sensor Probe may be unattached’}

nodeId 0
firmware 24
battery 3.2844
battery_percent 97.7536945812808%
counter 2
sensor_type_id 181
source_address 0013A200423C9F24
sensor_type_string C1D2 Two Channel Vibration Plus
sensor_data {‘error’: ‘Error found, Sensor Probe may be unattached’}

I’m looking for reference materials for payload parsing. Where can I find resources to help with this?

We are not focusing development efforts on the Python library at this time. All software development is focused on our Node-Red library here:

Parsing for the type 181 is the same as type 81. However it looks like type 81 was never added to the Python library either. I would recommend referencing the Node-Red library for more information. You can also refer to this manual for more information:

Thank you,
Travis Elliott

Library written in JavaScript:

Payload structure for type 81:

Based on the above references, I modified ncd_enterprise.py and EnterpriseNCD-Example.py to retrieve data as follows:
#### Output ####

payload :  bytearray(b'\x00\x18\x03\xfc\t\x00\xb5\x0c\x00\x0c\t5\x00W\x00\x10\x00f\x00\x05\x03\x07\x02\xed\x02\xad\
x00^\x00\x12\x00\x80\x00\x06\x03\x07\x02\xed\x02\xad\x00m\x00\x15\x00\x8f\x00\x07\x03\n\x02\xed\x02 \x0c\x16J\x00\x
00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
Firmware Version: 24
Hour: 53
Minute: 0
Expected Packets: 16
Current Packet: 0
battery             : 3.2844
battery_percent     : 97.7536945812808%
counter             : 9
firmware            : 24
nodeId              : 0

sensor_data:
  mode                : 0
  s1_odr              : 3200Hz
  s1_temperature      : 23.57
  s2_odr              : 3200Hz
  s2_temperature      : 57.06
  x1_displacement_mm  : 0.05
  x1_max_ACC_G        : 0.016
  x1_peak_one_Hz      : 775
  x1_peak_three_Hz    : 685
  x1_peak_two_Hz      : 749
  x1_rms_ACC_G        : 0.087
  x1_velocity_mm_sec  : 1.02
  x2_displacement_mm  : 0.0
  x2_max_ACC_G        : 0.0
  x2_peak_one_Hz      : 0
  x2_peak_three_Hz    : 0
  x2_peak_two_Hz      : 0
  x2_rms_ACC_G        : 0.0
  x2_velocity_mm_sec  : 0.0
  y1_displacement_mm  : 0.06
  y1_max_ACC_G        : 0.018
  y1_peak_one_Hz      : 775
  y1_peak_three_Hz    : 685
  y1_peak_two_Hz      : 749
  y1_rms_ACC_G        : 0.094
  y1_velocity_mm_sec  : 1.28
  y2_displacement_mm  : 0.0
  y2_max_ACC_G        : 0.0
  y2_peak_one_Hz      : 0
  y2_peak_three_Hz    : 0
  y2_peak_two_Hz      : 0
  y2_rms_ACC_G        : 0.0
  y2_velocity_mm_sec  : 0.0
  z1_displacement_mm  : 0.07
  z1_max_ACC_G        : 0.021
  z1_peak_one_Hz      : 778
  z1_peak_three_Hz    : 544
  z1_peak_two_Hz      : 749
  z1_rms_ACC_G        : 0.109
  z1_velocity_mm_sec  : 1.43
  z2_displacement_mm  : 0.0
  z2_max_ACC_G        : 0.0
  z2_peak_one_Hz      : 0
  z2_peak_three_Hz    : 0
  z2_peak_two_Hz      : 0
  z2_rms_ACC_G        : 0.0
  z2_velocity_mm_sec  : 0.0
sensor_type_id      : 181
sensor_type_string  : C1D2 Two Channel Vibration Plus
source_address      : 0013A200423C9F24

#### Modified CODE ######

diff --git a/ncd_enterprise.py b/ncd_enterprise.py
index 5a04d7a..63d2712 100644
--- a/ncd_enterprise.py
+++ b/ncd_enterprise.py
@@ -135,6 +135,9 @@ class NCDEnterprise:
         self.callback(parsed)
 
     def sensor_data(self, payload, source_address):
+        print("payload : ", payload)
+        otf_devices = [23, 26, 32, 33, 39, 44, 45, 48, 52, 53, 58, 74, 76, 78, 79, 80, 81, 82, 84, 88, 89, 90, 91, 97, 98, 101, 102, 105, 106, 107, 108, 110, 111, 112, 114, 118, 180, 181, 202, 519, 520, 521, 531, 535, 537, 538, 539, 540, 1010, 1011]
+
         parsed = {
             'nodeId': payload[0],
             'firmware': payload[1],
@@ -148,6 +151,11 @@ class NCDEnterprise:
             if(parsed['sensor_type_id'] == 80):
                 parsed['sensor_type_string'] = 'One Channel Vibration Plus'
                 parsed['sensor_data'] = type80(payload, parsed, source_address)
+            elif parsed['sensor_type_id'] == 181:
+                # Add specific handling for sensor type 181
+                parsed['sensor_type_string'] = 'C1D2 Two Channel Vibration Plus'
+                # need type181
+                parsed['sensor_data'] = type181(payload, parsed, source_address)
 
             else:
                 parsed['sensor_type_string'] = self.sensor_types[str(parsed['sensor_type_id'])]['name']
@@ -571,6 +579,10 @@ def sensor_types():
                        'name': 'Predictive Maintenance Sensor',
             'parse': lambda d :        'This is a more complex sensor and so is parsed using the defined function "type80"'
                },
+        '181': {
+                       'name': 'C1D2 Two Channel Vibration Plus Sensor',
+            'parse': lambda d :        'This is a more complex sensor and so is parsed using the defined function "type181"'
+               },
         # unsupported
                # '10000':{
         #
@@ -605,6 +617,113 @@ def sensor_types():
                # }
        }
     return types
+
+def type181(payload, parsed, mac):
+    sensor_data = {}
+    if payload[7] & 2:
+        sensor_data['probe_1_error'] = True
+    if payload[7] & 4:
+        sensor_data['probe_2_error'] = True
+    if (payload[7] & 2) and (payload[7] & 4):
+        return sensor_data
+
+    odr_translate_dict = {
+        6: '50Hz',
+        7: '100Hz',
+        8: '200Hz',
+        9: '400Hz',
+        10: '800Hz',
+        11: '1600Hz',
+        12: '3200Hz',
+        13: '6400Hz',
+        14: '12800Hz',
+        25: '25600Hz',
+    }
+    if(payload[8] == 0 or payload[8] == 2):
+        #deviceAddr = mac
+        firmware = payload[1]
+        hour = payload[11]
+        minute = payload[12]
+        expected_packets = payload[15]
+        current_packet = payload[16]
+        # Print statements to display the values
+        print(f"Firmware Version: {firmware}")
+        print(f"Hour: {hour}")
+        print(f"Minute: {minute}")
+        print(f"Expected Packets: {expected_packets}")
+        print(f"Current Packet: {current_packet}")
+        sdata_start = 17
+        odr1 = odr_translate_dict[payload[9]]
+        odr2 = odr_translate_dict[payload[54]]
+        sensor_data = {
+            'mode': payload[8],
+
+            # Sensor 1 data
+            's1_odr': odr1,
+            's1_temperature': signInt(reduce(msbLsb, payload[10:12]), 16) / 100,
+
+            # Sensor 1 - X-axis
+            'x1_rms_ACC_G': reduce(msbLsb, payload[12:14]) / 1000,
+            'x1_max_ACC_G': reduce(msbLsb, payload[14:16]) / 1000,
+            'x1_velocity_mm_sec': reduce(msbLsb, payload[16:18]) / 100,
+            'x1_displacement_mm': reduce(msbLsb, payload[18:20]) / 100,
+            'x1_peak_one_Hz': reduce(msbLsb, payload[20:22]),
+            'x1_peak_two_Hz': reduce(msbLsb, payload[22:24]),
+            'x1_peak_three_Hz': reduce(msbLsb, payload[24:26]),
+
+            # Sensor 1 - Y-axis
+            'y1_rms_ACC_G': reduce(msbLsb, payload[26:28]) / 1000,
+            'y1_max_ACC_G': reduce(msbLsb, payload[28:30]) / 1000,
+            'y1_velocity_mm_sec': reduce(msbLsb, payload[30:32]) / 100,
+            'y1_displacement_mm': reduce(msbLsb, payload[32:34]) / 100,
+            'y1_peak_one_Hz': reduce(msbLsb, payload[34:36]),
+            'y1_peak_two_Hz': reduce(msbLsb, payload[36:38]),
+            'y1_peak_three_Hz': reduce(msbLsb, payload[38:40]),
+
+            # Sensor 1 - Z-axis
+            'z1_rms_ACC_G': reduce(msbLsb, payload[40:42]) / 1000,
+            'z1_max_ACC_G': reduce(msbLsb, payload[42:44]) / 1000,
+            'z1_velocity_mm_sec': reduce(msbLsb, payload[44:46]) / 100,
+            'z1_displacement_mm': reduce(msbLsb, payload[46:48]) / 100,
+            'z1_peak_one_Hz': reduce(msbLsb, payload[48:50]),
+            'z1_peak_two_Hz': reduce(msbLsb, payload[50:52]),
+            'z1_peak_three_Hz': reduce(msbLsb, payload[52:54]),
+
+            # Sensor 2 data
+            's2_odr': odr2,
+            's2_temperature': signInt(reduce(msbLsb, payload[55:57]), 16) / 100,
+
+            # Sensor 2 - X-axis
+            'x2_rms_ACC_G': reduce(msbLsb, payload[57:59]) / 1000,
+            'x2_max_ACC_G': reduce(msbLsb, payload[59:61]) / 1000,
+            'x2_velocity_mm_sec': reduce(msbLsb, payload[61:63]) / 100,
+            'x2_displacement_mm': reduce(msbLsb, payload[63:65]) / 100,
+            'x2_peak_one_Hz': reduce(msbLsb, payload[65:67]),
+            'x2_peak_two_Hz': reduce(msbLsb, payload[67:69]),
+            'x2_peak_three_Hz': reduce(msbLsb, payload[69:71]),
+
+            # Sensor 2 - Y-axis
+            'y2_rms_ACC_G': reduce(msbLsb, payload[71:73]) / 1000,
+            'y2_max_ACC_G': reduce(msbLsb, payload[73:75]) / 1000,
+            'y2_velocity_mm_sec': reduce(msbLsb, payload[75:77]) / 100,
+            'y2_displacement_mm': reduce(msbLsb, payload[77:79]) / 100,
+            'y2_peak_one_Hz': reduce(msbLsb, payload[79:81]),
+            'y2_peak_two_Hz': reduce(msbLsb, payload[81:83]),
+            'y2_peak_three_Hz': reduce(msbLsb, payload[83:85]),
+            # Sensor 2 - Z-axis
+            'z2_rms_ACC_G': reduce(msbLsb, payload[85:87]) / 1000,
+            'z2_max_ACC_G': reduce(msbLsb, payload[87:89]) / 1000,
+            'z2_velocity_mm_sec': reduce(msbLsb, payload[89:91]) / 100,
+            'z2_displacement_mm': reduce(msbLsb, payload[91:93]) / 100,
+            'z2_peak_one_Hz': reduce(msbLsb, payload[93:95]),
+            'z2_peak_two_Hz': reduce(msbLsb, payload[95:97]),
+            'z2_peak_three_Hz': reduce(msbLsb, payload[97:99]),
+            }
+        return sensor_data
+    else:
+        return payload
+
+

Next Steps:

  1. Modify the type181() function added to ncd_enterprise.py, referencing WirelessGateway.js, to handle Fly Packet processing and other updates.
  2. Update EnterpriseNCD-Configuration-Example.py to allow configuration changes such as sleep time adjustments.

My project requires real-time vibration monitoring during vehicle operation, but currently, packets are transmitted approximately every 10 minutes.

Do you have any materials or resources that could help with these tasks?
The project timeline is very tight, and I need to finish everything by the end of December. :cry:

It looks like you are making great progress.

Our vibration sensors have a minimum report interval of 5 minutes. They do not have the capability to report data continuously as it is not applicable to their designed application(machine health monitoring).

If the minimum interval is 5 minutes, is there any way to modify this minimum interval?

Alternatively, is there a test firmware available? Since the sensors have already been purchased, the project might be halted if real-time operation isn’t achievable.

We plan to install two 2-channel sensors on a single vehicle to collect vibration data from each wheel for AI training purposes. This solution is not intended for long-term installation. Collecting data for 2–3 hours a day is sufficient for our needs. Is there a way to aggregate RAW data and transmit it at specific intervals?

Hi @kyrha75

No, unfortunately this is not possible. The minimum report interval is 5 minutes.

Please let us know if you have any other questions.

Thank you,
Travis

Then, can you tell me how to request and receive RAW Data in real time?
I am checking the document below, and 80 is the standard, and I am getting an import error, so I cannot add it to nod-red.

Can you provide screen shots of the error you are seeing?

Also can you tell me the version of the node-red-enterise-sensors library installed, the version of Node-Red installed, and the version of NodeJS you are using?

Thank you,
Travis Elliott

Hi @kyrha75, We encountered an issue with the JSON format in the code example for the raw request flow (in the article you mentioned). I’m attaching the flow for type 80 as a reference, you should be able to import it into your Node-RED project.

[{"id":"fa7025f6.6cd218","type":"ncd-gateway-node","z":"8c51d1a367a55d26","name":"","connection":"","unknown_devices":false,"outputs":1,"x":430,"y":480,"wires":[["afa54b75.0b7908","1d25ecf9.d21c43"]]},{"id":"f58cd0ab.1993b","type":"inject","z":"8c51d1a367a55d26","name":"set MAC address device t80","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"devices\":[\"00:13:a2:00:41:ba:c4:05\"]}","payloadType":"json","x":280,"y":380,"wires":[["361cbe74.8a7d12"]]},{"id":"361cbe74.8a7d12","type":"function","z":"8c51d1a367a55d26","name":"Add incomming device to context","func":"var newDevices = msg.payload.devices;\nvar existingDevices = flow.get('devices');\n\nif(typeof existingDevices == 'undefined'){\n    flow.set('devices',newDevices);\n    msg.payload.devices = newDevices;\n    return msg;\n}else{\n    for(let newDevice of newDevices){\n        let exists = false;\n        for(let existingDevice of existingDevices){\n            if(newDevice == existingDevice){\n                exists = true;\n                break;\n            }\n        }\n        if(!exists){\n            existingDevices.push(newDevice);\n        } \n    }\n}\nflow.set('devices',existingDevices);\nmsg.payload.devices = existingDevices;\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":570,"y":380,"wires":[[]]},{"id":"574ec32a.66499c","type":"function","z":"8c51d1a367a55d26","name":"ncd-check-and-RAW-request","func":"var devices = flow.get('devices');\nvar ramdevices = flow.get('ramdevices');\nvar deviceID = msg.payload.addr;\nif(typeof devices == 'undefined')return;\nif(typeof ramdevices == 'undefined'){\n    ramdevices = [];\n    flow.set('ramdevices',ramdevices);\n    \n}\nfor(let i = 0; i < devices.length; i++){\n    // console.log('device: '+devices[i]+' deviceID: '+deviceID);\n    if(deviceID == devices[i] && msg.payload.sensor_data.hasOwnProperty(\"x_rms_ACC_G\")){\n        if(ramdevices.includes(deviceID)){\n          msg.payload.address = deviceID;  \n        }\n        else{\n           msg.payload.address = \"00:00:00:00:00:00:ff:ff\" ;\n           ramdevices.push(deviceID);\n        }\n        // msg.payload.address = deviceID;\n        // msg.payload.address = \"00:00:00:00:00:00:ff:ff\"\n        msg.payload.data = [0xF4,0x4F,0x00,0x00,0x54,0x13]; // send request RAW for type 80\n        // devices.splice(i,1);\n        flow.set(\"ramdevices\",ramdevices);\n        return msg;\n        \n    }\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":920,"y":480,"wires":[["742ac12.b30874","12ba62b2.90163d"]]},{"id":"1d25ecf9.d21c43","type":"switch","z":"8c51d1a367a55d26","name":"ncd-check-sensor-type-80","property":"payload.sensor_type","propertyType":"msg","rules":[{"t":"eq","v":"80","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":670,"y":480,"wires":[["574ec32a.66499c"]]},{"id":"12ba62b2.90163d","type":"delay","z":"8c51d1a367a55d26","name":"","pauseType":"delay","timeout":"100","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":230,"y":480,"wires":[["fa7025f6.6cd218"]]},{"id":"742ac12.b30874","type":"debug","z":"8c51d1a367a55d26","name":"check device","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1150,"y":480,"wires":[]},{"id":"afa54b75.0b7908","type":"debug","z":"8c51d1a367a55d26","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":630,"y":440,"wires":[]}]

Please follow the same instructions to implement it. Ensure that the Vibration Sensor is set to the ‘Processed + Raw on Demand’ mode before using the command. Additionally, we strongly recommend setting the destination address if you plan to work with RAW sensor data.

Additionally, I’m sharing an article that explains how to configure sensor parameters using Node-RED.

Please try this and let me know if you have any questions.
Thank you,
Eduardo M.

First of all, thank you Eduardo M… I added and set up the node as below with the json you provided.

The Wireless Device node was set up as follows.




The ncd-check-and-RAW-request function node was modified to match number 181 as shown below.

Inject the node with the address value of the sensor.

ncd-check-sensor-type was changed from 80 to 181.

As below, I was able to confirm that 3400 raw data for probe 1 were coming in once every 5 minutes.

After thinking about it, I think that raw data is data collected when the sensor wakes up once every 5 minutes, 3400 pieces at that moment, and it doesn’t seem to be continuous raw data for 5 minutes. Is there no way to receive data for 1 second once every second?

How about connecting a function node to the Sensor that requests raw data every second using a trigger node while it’s awake for 2 seconds in raw mode?

Hi @kyrha75 Unfortunately, the minimum reporting interval is 5 minutes, and you can only send a RAW data request command when the sensor is awake (When the sensor sends processed data it wait for the RAW data mode request after and if there is none, it goes back to sleep. The default wait time is 2 seconds).

We recommend only requesting RAW data when you absolutely need to. For example setting a threshold for the max acceleration what would trigger the RAW data request so you can perform a detailed spectral analysis, sending a RAW data packet consumes a lot of power as it is a very large packet, this can significantly reduce battery life. Additionally, as it is a large packet the time on air is longer and it as a greater change to cause interference, creating network congestion especially if there are a lot of sensor operating.

Please take a look at this and let us know if you have any questions.
Thank you,
Eduardo M.

Hi Eduardo,

Thank you for your detailed explanation. I understand that the minimum reporting interval is 5 minutes in processed mode and that requesting RAW data frequently can significantly reduce battery life due to the large packet size and increased air time. I also understand the potential for network congestion, especially with many sensors operating.

However, I would like to clarify my specific use case. I am planning to use only two sensors and will be collecting vibration data for short periods (a few hours to a day at most). I will also be replacing the batteries as needed. Therefore, the long-term battery life is not a primary concern for me.

Given this context, I would like to know if there is any way to bypass or reduce the 5-minute minimum reporting interval in processed mode when using only two sensors. Since I am prioritizing data acquisition over long-term battery life in this short-term experiment, a more frequent data sampling rate would be beneficial.

Thank you for your time and assistance.

we can build a custom firmware which can send data every 1 min. will that work ?

I think it should be less than 1 minute because it is intended to be installed in a car and collect data.
I should have checked the product specifications carefully before purchasing it, but I made a mistake. I am disappointed with the support for a product that costs over $600(PR55-61N *2 + usb modem) . I will give up using the 181 type sensor. Thank you to everyone who has responded so far.

We will build a new firmware for you it will do this

As soon it detects vibration it will start sampling and send data as fast as it can. Once vibration is done it will go back to sleep and keep repeating

This way you can get data as fast it can send and still not drain battery

here you go
this firmware will send data as fast as it can