import numpy as np, sys fs=4e6; SPC=2 # samples per chip C0='11011001110000110101001000101110' def chips(s): b=[int(c) for c in C0] k=s&7 b=b[-4*k:]+b[:-4*k] if k else b if s>=8: b=[v^(i&1) for i,v in enumerate(b)] return b CH=[chips(s) for s in range(16)] def modulate(chipseq): n=len(chipseq) L=(n+2)*SPC I=np.zeros(L);Q=np.zeros(L) p=np.sin(np.pi*np.arange(2*SPC)/(2*SPC)) for i,c in enumerate(chipseq): v=2*c-1; st=i*SPC if i%2==0: I[st:st+2*SPC]+=v*p else: Q[st:st+2*SPC]+=v*p return I+1j*Q def fdisc(x): return np.angle(x[1:]*np.conj(x[:-1])) # templates of freq per symbol (64 samples), from symbol embedded in random context rng=np.random.default_rng(1) T=np.zeros((16,64)) for s in range(16): acc=np.zeros(64) for _ in range(20): a,b=rng.integers(16,size=2) seq=CH[a]+CH[s]+CH[b] f=fdisc(modulate(seq)) acc+=f[64:128] T[s]=acc/20 T-=T.mean(1,keepdims=True) Tn=T/np.linalg.norm(T,axis=1,keepdims=True) def demod_from(f,st): syms=[];pos=st while pos+64+2<=len(f): best=None for d in (-1,0,1): p=pos+d if p<0: continue r=Tn@f[p:p+64] k=int(np.argmax(r)) if best is None or r[k]>best[0]: best=(r[k],k,p) r,k,p=best if r<0.3*np.linalg.norm(f[p:p+64]): break syms.append(k);pos=p+64 return syms def parse(syms): for i in range(1,len(syms)-1): if syms[i]==7 and syms[i+1]==0xA and syms[i-1]==0: b=syms[i+2:] by=bytes((b[j]|(b[j+1]<<4)) for j in range(0,len(b)-1,2)) if not by: return None L=by[0]&0x7f return by[1:1+L] return None def demod_burst(x): f=fdisc(x); f=f-np.median(f) pre=np.tile(Tn[0],4) n=min(len(f),3500) c=np.correlate(f[:n],pre,'valid') c=c/(np.sqrt(np.convolve(f[:n]**2,np.ones(len(pre)),'valid'))+1e-9) cand=np.flatnonzero(c>0.7*c.max()) tried=set();first=None for st in cand: st=int(st) if any(abs(st-t)<3 for t in tried): continue tried.add(st) fr=parse(demod_from(f,st)) if fr is None: continue if first is None: first=fr if len(fr)>=2 and crc16(fr[:-2])==int.from_bytes(fr[-2:],'little'): return fr if len(tried)>40: break return first def frames(path): raw=np.fromfile(path,dtype=np.int8).astype(np.float32) x=raw[0::2]+1j*raw[1::2] p=np.abs(x)**2 e=np.convolve(p,np.ones(400)/400,'same') on=e>np.median(e)*10 idx=np.flatnonzero(np.diff(on.astype(int))) for s,t in zip(idx[0::2],idx[1::2]): s=max(0,s-600);t=t+300 yield s/fs,demod_burst(x[s:t]) def crc16(d): c=0 for b in d: c^=b for _ in range(8): c=(c>>1)^0x8408 if c&1 else c>>1 return c if __name__=='__main__': for t,fr in frames(sys.argv[1]): if fr is None: print(f'{t:.4f} ??');continue ok=len(fr)>=2 and crc16(fr[:-2])==int.from_bytes(fr[-2:],'little') print(f'{t:.4f} {"OK " if ok else "BAD"} {len(fr):3d} {fr.hex()}')