public void imm_handleTimedNotification(YPktStreamHead data)
        {
            uint    pos  = 0;
            YDevice ydev = _yctx._yHash.imm_getDevice(SerialNumber);

            if (ydev == null)
            {
                // device has not been registered;
                return;
            }

            while (pos < data.Len)
            {
                int  funYdx = data.imm_GetByte(pos) & 0xf;
                bool isAvg  = (data.imm_GetByte(pos) & 0x80) != 0;
                uint len    = (uint)(1 + ((data.imm_GetByte(pos) >> 4) & 0x7));
                pos++;
                if (data.Len < pos + len)
                {
                    _yctx._Log("drop invalid timedNotification");
                    return;
                }

                if (funYdx == 0xf)
                {
                    byte[] intData = new byte[len];
                    for (uint i = 0; i < len; i++)
                    {
                        intData[i] = data.imm_GetByte(pos + i);
                    }

                    ydev.imm_setDeviceTime(intData);
                }
                else
                {
                    YPEntry yp = imm_getYPEntryFromYdx(funYdx);
                    if (yp != null)
                    {
                        List <int> report = new List <int>((int)(len + 1));
                        report.Add(isAvg ? 1 : 0);
                        for (uint i = 0; i < len; i++)
                        {
                            int b = data.imm_GetByte(pos + i) & 0xff;
                            report.Add(b);
                        }

                        _hub.imm_handleTimedNotification(yp.Serial, yp.FuncId, ydev.imm_getDeviceTime(), report);
                    }
                }

                pos += len;
            }
        }
        public void handleTimedNotificationV2(YPktStreamHead data)
        {
            uint    pos  = 0;
            YDevice ydev = _yctx._yHash.imm_getDevice(SerialNumber);

            if (ydev == null)
            {
                // device has not been registered;
                return;
            }

            while (pos < data.Len)
            {
                int  funYdx   = data.imm_GetByte(pos) & 0xf;
                uint extralen = (uint)((data.imm_GetByte(pos) >> 4) & 0xf);
                uint len      = extralen + 1;
                pos++; // consume generic header
                if (funYdx == 0xf)
                {
                    byte[] intData = new byte[len];
                    for (uint i = 0; i < len; i++)
                    {
                        intData[i] = data.imm_GetByte(pos + i);
                    }

                    ydev.imm_setDeviceTime(intData);
                }
                else
                {
                    YPEntry yp = imm_getYPEntryFromYdx(funYdx);
                    if (yp != null)
                    {
                        List <int> report = new List <int>((int)(len + 1));
                        report.Add(2);
                        for (uint i = 0; i < len; i++)
                        {
                            int b = data.imm_GetByte(pos + i) & 0xff;
                            report.Add(b);
                        }

                        _hub.imm_handleTimedNotification(yp.Serial, yp.FuncId, ydev.imm_getDeviceTime(), report);
                    }
                }

                pos += len;
            }
        }
        protected internal virtual void handleNetNotification(string notification_line)
        {
            string ev = notification_line.Trim();

            if (ev.Length >= 3 && ev[0] >= NOTIFY_NETPKT_FLUSHV2YDX && ev[0] <= NOTIFY_NETPKT_TIMEAVGYDX)
            {
                // function value ydx (tiny notification)
                _hub._devListValidity = 10000;
                _notifRetryCount      = 0;
                if (_notifyPos >= 0)
                {
                    _notifyPos += ev.Length + 1;
                }
                int devydx = ev[1] - 65; // from 'A'
                int funydx = ev[2] - 48; // from '0'

                if ((funydx & 64) != 0)  // high bit of devydx is on second character
                {
                    funydx -= 64;
                    devydx += 128;
                }
                string value = ev.Substring(3);
                if (_hub._serialByYdx.ContainsKey(devydx))
                {
                    string  serial = _hub._serialByYdx[devydx];
                    string  funcid;
                    YDevice ydev = _hub._yctx._yHash.imm_getDevice(serial);
                    if (ydev != null)
                    {
                        switch (ev[0])
                        {
                        case NOTIFY_NETPKT_FUNCVALYDX:
                            funcid = ydev.imm_getYPEntry(funydx).FuncId;
                            if (!funcid.Equals(""))
                            {
                                // function value ydx (tiny notification)
                                _hub.imm_handleValueNotification(serial, funcid, value);
                            }
                            break;

                        case NOTIFY_NETPKT_DEVLOGYDX:
                            //fixme:
                            //await ydev.triggerLogPull();
                            break;

                        case NOTIFY_NETPKT_TIMEVALYDX:
                        case NOTIFY_NETPKT_TIMEAVGYDX:
                        case NOTIFY_NETPKT_TIMEV2YDX:
                            if (funydx == 0xf)
                            {
                                byte[] data = new byte[5];
                                for (int i = 0; i < 5; i++)
                                {
                                    string part = value.Substring(i * 2, 2);
                                    data[i] = Convert.ToByte(part, 16);
                                }
                                ydev.imm_setDeviceTime(data);
                            }
                            else
                            {
                                funcid = ydev.imm_getYPEntry(funydx).FuncId;
                                if (!funcid.Equals(""))
                                {
                                    // timed value report
                                    List <int> report = new List <int>(1 + value.Length / 2);
                                    report.Add((ev[0] == NOTIFY_NETPKT_TIMEVALYDX ? 0 : (ev[0] == NOTIFY_NETPKT_TIMEAVGYDX ? 1 : 2)));
                                    for (int pos = 0; pos < value.Length; pos += 2)
                                    {
                                        int intval = Convert.ToInt32(value.Substring(pos, 2), 16);
                                        report.Add(intval);
                                    }
                                    _hub.imm_handleTimedNotification(serial, funcid, ydev.imm_getDeviceTime(), report);
                                }
                            }
                            break;

                        case NOTIFY_NETPKT_FUNCV2YDX:
                            funcid = ydev.imm_getYPEntry(funydx).FuncId;
                            if (!funcid.Equals(""))
                            {
                                byte[] rawval = decodeNetFuncValV2(YAPI.DefaultEncoding.GetBytes(value));
                                if (rawval != null)
                                {
                                    string decodedval = YGenericHub.imm_decodePubVal(rawval[0], rawval, 1, 6);
                                    // function value ydx (tiny notification)
                                    _hub.imm_handleValueNotification(serial, funcid, decodedval);
                                }
                            }
                            break;

                        case NOTIFY_NETPKT_FLUSHV2YDX:
                            // To be implemented later
                            break;
                        }
                    }
                }
            }
            else if (ev.Length >= 5 && ev.StartsWith("YN01", StringComparison.Ordinal))
            {
                _hub._devListValidity = 10000;
                _notifRetryCount      = 0;
                if (_notifyPos >= 0)
                {
                    _notifyPos += ev.Length + 1;
                }
                char notype = ev[4];
                if (notype == NOTIFY_NETPKT_NOT_SYNC)
                {
                    _notifyPos = Convert.ToInt32(ev.Substring(5));
                }
                else
                {
                    switch (notype)
                    {
                    case NOTIFY_NETPKT_NAME:        // device name change, or arrival
                    case NOTIFY_NETPKT_CHILD:       // device plug/unplug
                    case NOTIFY_NETPKT_FUNCNAME:    // function name change
                    case NOTIFY_NETPKT_FUNCNAMEYDX: // function name change (ydx)
                        _hub._devListExpires = 0;
                        break;

                    case NOTIFY_NETPKT_FUNCVAL:     // function value (long notification)
                        string[] parts = ev.Substring(5).Split(',');
                        _hub.imm_handleValueNotification(parts[0], parts[1], parts[2]);
                        break;
                    }
                }
            }
            else
            {
                // oops, bad notification ? be safe until a good one comes
                _hub._devListValidity = 500;
                _notifyPos            = -1;
            }
        }