How it was translated the protocol of the Victor 86C

The multimeter Victor 86 transmits its values through an optical interface in its upper part. In this interface can be plugged a connector that converts these optical signals and delivers them to your computer via USB every second or so. When data arrives to your computer via USB is in the form of packets of 12 byte (more or less) and there isn’t a protocol to translate this stream of bytes in useful values. The method I used to decipher the USB interface “Protocol” was simple but very time consuming and tiring. I made sure the multimeter displayed a constant value, such as the temperature, and I recorded the bytes that were sent through the interface. Subsequently, I raised the temperature by one degree, from 20 to 21° C, and I recorded the values again.

Repeating several times this procedure and with different functions I found that the meter does not send numerical values (e.g. does not send the value 20° C), but communicates a kind of snapshot of the display, piece by piece (it sends the 2, the zero and the degree symbol). This of course made the process easier, but also longer, because it was necessary to assign every bit to a function or value on the multimeter.

Eventually I came to build a sort of table that compared changes in values of the multimeter with changes in bytes of data received, understanding finally to which bits corresponds every display component, and I was able to build the decoding class given below:

public class Parser
    {
        /* Variable LIST */
        /* INPUT */
        public Byte[] inputBytes;
        /* OUTPUT */
        public string Value; //string value with subunit of measure (e.g. 100 mV, 1000 nF, 2 μA)
        public Double numValue; //numeric value relative to subunit of measure
        public Double RectifiedValue; //numeric value with no subunit (e.g. 100 mV ---> 0.100 V, 1000 nF --> 0.000001 F)
        public bool IsAC;
        public bool IsDC;
        public bool IsMax;
        public bool IsMin;
        public bool IsRel;
        public bool IsHold;
        public bool IsOutOfRange;
        public bool IsAutoSub;
        public string UnitOfMeasure; //Unit of measure
        public string LongUnitOfMeasure; //Not abbreviated unit of measure string. e.g. Voltage, Hertz
        public string ACDC;

        public Parser()
        {
            //Declaration of all variables
            inputBytes = null;
            Value = UnitOfMeasure = LongUnitOfMeasure = ACDC = "";
            numValue = 0;
            RectifiedValue = 0;
            IsAC = IsDC = IsAutoSub = IsHold = IsMax = IsMin = IsOutOfRange = IsRel = false;
        }

        public void UpdateResult()
        {
            if (inputBytes!= null)
            {
                Value = UnitOfMeasure = LongUnitOfMeasure = ACDC = "";
                numValue = 0;
                RectifiedValue = 0;
                IsAC = IsDC = IsAutoSub = IsHold = IsMax = IsMin = IsOutOfRange = IsRel = false;
                string byte7, byte10, byte4, byte11 = "";
                // Retrieving number displayed
                byte7 = Convert.ToString(inputBytes[7], 2).PadLeft(8, '0').Substring(0, 4);
                byte10 = Convert.ToString(inputBytes[10], 2).PadLeft(8, '0').Substring(0, 4);
                byte4 = Convert.ToString(inputBytes[4], 2).PadLeft(8, '0').Substring(0, 4);
                byte11 = Convert.ToString(inputBytes[11], 2).PadLeft(8, '0').Substring(0, 4);
                numValue = Parse7(byte7) + (Parse10(byte10) * 10) + (Parse4(byte4) * 100) + (Parse11(byte11) * 1000);
                numValue *= ParseSign(Convert.ToString(inputBytes[2], 2).PadLeft(8, '0').Substring(1, 2));
                // Setting the decimal point in the number
                int commapos = CommaPos(Convert.ToString(inputBytes[6], 2).PadLeft(8, '0').Substring(0, 4));
                if (commapos == 3)
                {
                    numValue /= 1000;
                    Value = numValue.ToString("F3");
                }
                else if (commapos == 2)
                {
                    numValue /= 100;
                    Value = numValue.ToString("F2");
                }
                else if (commapos == 1)
                {
                    numValue /= 10;
                    Value = numValue.ToString("F1");
                }
                else Value = numValue.ToString("F0");
                RectifiedValue = numValue;
                //Setting the RectifiedValue by getting SubUnit of Measure
                if (ParseSub(Convert.ToString(inputBytes[13], 2).PadLeft(8, '0')) == "m")
                    RectifiedValue /= 1000;
                else if (ParseSub(Convert.ToString(inputBytes[13], 2).PadLeft(8, '0')) == "μ")
                    RectifiedValue /= 1000000;
                //Setting Unit and SubUnit of measure in string Value output
                UnitOfMeasure = ParseMode(Convert.ToString(inputBytes[9], 2).PadLeft(8, '0'));
                if (UnitOfMeasure == "Ω")
                {
                    Value += (" " + ParseOhmMode(Convert.ToString(inputBytes[13], 2).PadLeft(8, '0')) + "Ω");
                    if (ParseOhmMode(Convert.ToString(inputBytes[13], 2).PadLeft(8, '0')) == "k") RectifiedValue *= 1000;
                    else if (ParseOhmMode(Convert.ToString(inputBytes[13], 2).PadLeft(8, '0')) == "M") RectifiedValue *= 1000000;
                }
                else if (UnitOfMeasure == "F")
                {
                    if (ParseNano(Convert.ToString(inputBytes[3], 2).PadLeft(8, '0').Substring(0, 4)))
                    {
                        Value += (" nF");
                        RectifiedValue /= 1000000000;
                    }
                    else Value += (" " + ParseSub(Convert.ToString(inputBytes[13], 2).PadLeft(8, '0')) + "F");
                }
                else Value += (" " + ParseSub(Convert.ToString(inputBytes[13], 2).PadLeft(8, '0')) + UnitOfMeasure);
                if (Parse10(Convert.ToString(inputBytes[10], 2).PadLeft(8, '0').Substring(0, 4)) == 99)
                    IsOutOfRange = true;
                
                // Setting AC/DC and Auto
                if ((UnitOfMeasure == "A") || (UnitOfMeasure == "V"))
                {
                    if (ParseACDC(Convert.ToString(inputBytes[1], 2).PadLeft(8, '0')) == "AC")
                    {
                        IsAC = true;
                        IsDC = false;
                    }
                    else if (ParseACDC(Convert.ToString(inputBytes[1], 2).PadLeft(8, '0')) == "DC")
                    {
                        IsDC = true;
                        IsAC = false;
                    }
                }
                if (IsAuto(Convert.ToString(inputBytes[1], 2).PadLeft(8, '0')))
                    IsAutoSub = true;
                else IsAutoSub = false;
                // Setting Rel and HOLD
                IsRel = ParseRel(Convert.ToString(inputBytes[1], 2).PadLeft(8, '0'));
                if (ParseHold(Convert.ToString(inputBytes[1], 2).PadLeft(8, '0')) && !IsRel)
                    IsHold = true;
                else if (ParseHold(Convert.ToString(inputBytes[1], 2).PadLeft(8, '0')) && IsRel)
                    IsHold = false;
                else if (!ParseHold(Convert.ToString(inputBytes[1], 2).PadLeft(8, '0')) && IsRel)
                    IsHold = true;
                else if (!ParseHold(Convert.ToString(inputBytes[1], 2).PadLeft(8, '0')) && !IsRel)
                    IsHold = false;
                //Setting OUT OF RANGE
                if (Parse10(Convert.ToString(inputBytes[10], 2).PadLeft(8, '0').Substring(0, 4)) == 99)
                    IsOutOfRange = true;
                else IsOutOfRange = false;
                //Setting extended Unit of Measure name
                if (ParseMode(Convert.ToString(inputBytes[9], 2).PadLeft(8, '0')) == "A")
                {
                    LongUnitOfMeasure = "Amperes";
                }
                else if (ParseMode(Convert.ToString(inputBytes[9], 2).PadLeft(8, '0')) == "V")
                {
                    LongUnitOfMeasure = "Volts";
                }
                else if (ParseMode(Convert.ToString(inputBytes[9], 2).PadLeft(8, '0')) == "°C")
                {
                    LongUnitOfMeasure = "Celsius Degrees";
                }
                else if (ParseMode(Convert.ToString(inputBytes[9], 2).PadLeft(8, '0')) == "°F")
                {
                    LongUnitOfMeasure = "Fahrenheit Degrees";
                }
                else if (ParseMode(Convert.ToString(inputBytes[9], 2).PadLeft(8, '0')) == "Hz")
                {
                    LongUnitOfMeasure = "Hertz";
                }
                else if (ParseMode(Convert.ToString(inputBytes[9], 2).PadLeft(8, '0')) == "F")
                {
                    LongUnitOfMeasure = "Farad";
                }
                else if (ParseMode(Convert.ToString(inputBytes[9], 2).PadLeft(8, '0')) == "Ω")
                {
                    LongUnitOfMeasure = "Ohm";
                }
                if(ParseMaxMin(Convert.ToString(inputBytes[3], 2).PadLeft(8, '0').Substring(4, 4))==1)
                {
                    IsMax = true;
                    IsMin = false;
                } else if (ParseMaxMin(Convert.ToString(inputBytes[3], 2).PadLeft(8, '0').Substring(4, 4)) == 2)
                {
                    IsMax = false;
                    IsMin = true;
                } else
                {
                    IsMax = false;
                    IsMin = false;
                }
            }
            else
            {
                throw new System.ArgumentException("You must first set data to process through the inputBytes Byte[] variable", "inputBytes");
            }
        }

        private int Parse7(string input)
        {
            if (input == "1000")
            {
                return 0;
            }
            else if (input == "0000")
            {
                return 1;
            }
            else if (input == "1100")
            {
                return 2;
            }
            else if (input == "0100")
            {
                return 3;
            }
            else if (input == "1010")
            {
                return 4;
            }
            else if (input == "0010")
            {
                return 5;
            }
            else if (input == "1110")
            {
                return 6;
            }
            else if (input == "0110")
            {
                return 7;
            }
            else if (input == "1001")
            {
                return 8;
            }
            else if (input == "0001")
            {
                return 9;
            }
            else return 0;
        }
        private int Parse10(string input)
        {
            if (input == "0110")
            {
                return 0;
            }
            else if (input == "1110")
            {
                return 1;
            }
            else if (input == "1010")
            {
                return 2;
            }
            else if (input == "0010")
            {
                return 3;
            }
            else if (input == "1000")
            {
                return 4;
            }
            else if (input == "0000")
            {
                return 5;
            }
            else if (input == "1100")
            {
                return 6;
            }
            else if (input == "0100")
            {
                return 7;
            }
            else if (input == "0111")
            {
                return 8;
            }
            else if (input == "1111")
            {
                return 9;
            } else if (input == "1011")
            {
                return 99;
            }
            else return 0;
        }
        private int Parse4(string input)
        {
            if (input == "0111")
            {
                return 0;
            }
            else if (input == "1111")
            {
                return 1;
            }
            else if (input == "1011")
            {
                return 2;
            }
            else if (input == "0011")
            {
                return 3;
            }
            else if (input == "1001")
            {
                return 4;
            }
            else if (input == "0001")
            {
                return 5;
            }
            else if (input == "1101")
            {
                return 6;
            }
            else if (input == "0101")
            {
                return 7;
            }
            else if (input == "1000")
            {
                return 8;
            }
            else if (input == "0000")
            {
                return 9;
            }
            else return 0;
        }
        private int Parse11(string input)
        {
            if (input == "0111")
            {
                return 0;
            }
            else if (input == "1111")
            {
                return 1;
            }
            else if (input == "1011")
            {
                return 2;
            }
            else if (input == "0011")
            {
                return 3;
            }
            else if (input == "1001")
            {
                return 4;
            }
            else return 0;
        }
        private int CommaPos(string input)
        {
            if (input == "0000")
            {
                return 3;
            }
            else if (input == "1100")
            {
                return 2;
            }
            else if (input == "1010")
            {
                return 1;
            }
            else return 0;
        }
        private Double ParseSign(string input)
        {
            if (input == "01")
            {
                return (-1);
            }
            else return 1;
        }
        private string ParseSub(string input)
        {
            if (input == "01101010")
            {
                return "μ";
            }
            else if (input == "01101011")
            {
                return "m";
            }
            else return "";
        }
        private string ParseMode(string input)
        {
            if (input == "01101010")
            {
                return "V";
            }
            else if (input == "01101011")
            {
                return "A";
            }
            else if (input == "10101001")
            {
                return "°C";
            }
            else if (input == "11101001")
            {
                return "°F";
            }
            else if (input == "01111001")
            {
                return "Hz";
            }
            else if (input == "10001001")
            {
                return "F";
            }
            else if (input == "01101101")
            {
                return "Ω";
            }
            else return "";
        }
        private string ParseACDC(string input)
        {
            if (input.Substring(4, 1) == "1")
                return "AC";
            else if (input.Substring(4, 1) == "0")
                return "DC";
            else return "";
        }
        private bool ParseHold(string input)
        {
            if (input.Substring(1, 1) == "1")
                return false;
            else if (input.Substring(1, 1) == "0")
                return true;
            else return false;
        }
        private Boolean IsAuto(string input)
        {
            if (input.Substring(5, 1) == "1")
                return true;
            else return false;
        }
        private string ParseOhmMode(string input)
        {
            if (input == "01101101") return "k";
            else if (input == "01110001") return "M";
            else return "";
        }
        private bool ParseNano(string input)
        {
            if (input == "1010")
            {
                return true;
            }
            else return false;
        }
        private int ParseMaxMin(string input)
        {
            if (input == "1000") return 1;
            else if (input == "1100") return 2;
            else return 0;
        }
        private bool ParseRel(string input)
        {
            if (input.Substring(2, 1) == "0") return true;
            else return false;
        }
    }

The core of this class is the function

UpdateResult()

which is responsible of taking incoming bytes and processing them to write in the different variables of the class the result, but the Parse functions do the dirty work, taking in the bytes and translating them to an actual value. Usage of this class is very simple and intuitive, just instantiate it, assign bytes received to the variable inputBytes and call the UpdateResult method.

The method will do everything and will put in the class variables a snapshot of the display of the meter.