標籤:

自製機械網速表

0. 想法

一直很想要一個 NAS,雖然知道對自己來說可能並不是很有用但是很想要啊,於是就組了一個。主板上的音效卡自然空閑著了,畢竟 NAS 要啥音效卡啊……

於是在想這個音效卡能有啥用…… 拿來放歌就有點沒意思了,做音頻監控的話,貌似家裡也沒啥可監控的……

音效卡的本質就是個 DAC,可以讓電腦輸入輸出模擬信號,只是太專用了。那麼有了 DAC 有啥信號可以輸入輸出呢?

家裡 NAS 同時也做路由,那就把音效卡接電流表,用來指示網速吧!

1. 關於硬體的嘗試

一開始以為是一個簡單的小製作,網速多少就直接輸出相應音量的音頻就是了,只不過音頻不是波形而是直流的。於是買了電流表、杜邦線和電阻。接好了以後,寫了一個小腳本用來播放直流的『聲音』,結果卻令人失望,電流表跳了一下,就回歸0位了,當關掉腳本的時候,電流表指針還會反向的跳一下。感覺是接了一個電容……?

查了一圈資料,發現音效卡確實會在 LINE OUT 上串一個『隔直電容』,用來濾除音效卡本身輸出的直流成分(是聲音,要啥直流啊)。

看到的帖子也說了,存在沒有接隔直電容的音效卡,於是就去淘寶上挑了兩個看起非常破爛的 USB 音效卡(背離初衷了2333),其中一個還是看著電路版上沒有大坨的電容才買的,結果買回來以後發現都有隔直電容……

以為是黑科技直接把電容做到晶元里了,所以決定走整流的路線,又去買了整流二極體和電容,做個一個橋式整流器。(寫文章的時候又去看了一下,發現隔直電容是能直接在電路板上看到的,都是貼片電容,直接短接應該可以搞定,之前沒仔細看……另外,後來發現橋式整流器有現成的組件,不用自己買二極體搭。畢竟不是搞電子的不知道這些東西)

整流器接好了以後,就可以正常的輸出了。

2. 程序

思路很簡單,通過讀取 /sys/class/net/<ifname>/statistics/{tx,rx}_bytes 獲得網卡的總的收發位元組數,除以時間獲得速度,然後根據速度輸出相應音量的音頻。

因為最後還是整流的方式,所以直接輸出正弦波就可以了,這裡正弦波的頻率選了 440 Hz,沒有什麼特別的理由,只是因為這個是標準音吧。

因為二極體的伏安特性並不是線性的,所以還要有個換算的過程,另外寫了程序來對應讀數和音量,最後的結果就是程序中的 SAMPLE 的列表。

畫個圖是這樣:

可以看到到後面基本就線性了,前面的幾個點就很蛋疼。

程序每 100ms 採樣一次,實際使用的時候,指針會使勁上下跳,於是加了指數加權平均(EWMA),這樣在有數據的時候指針會很快的跳到實際網速那裡,但是會花更長的時間退回到0,效果比較好。

最後,因為帶寬上限一般比較高,可以達到幾個M的樣子,但是一般的網路活動不會使用那麼多的帶寬,線性表示的話會導致絕大部分時間指針在0附近抖動,很沒意思。於是加入了對數尺度的選項。

代碼都貼在這裡了

gist.github.com/feisuzh

# -*- coding: utf-8 -*-nfrom __future__ import absolute_importnn# -- stdlib --nimport argparsenimport mathnimport timenn# -- third party --n# -- own --nimport numpy as npnimport pyaudionnn# -- code --nparser = argparse.ArgumentParser(retro-netmeter)nparser.add_argument(dev, type=str)nparser.add_argument(--soundcard, type=int, default=None)nparser.add_argument(--bandwidth, type=float, default=20.0)nparser.add_argument(--ewma-coeff, type=float, default=0.6)nparser.add_argument(--volume, type=float, default=1.0)nparser.add_argument(--log-scale, action=store_true)nparser.add_argument(--value, type=float, default=None)noptions = parser.parse_args()nnaudio = pyaudio.PyAudio()nstream = audio.open(n format=pyaudio.paFloat32,n channels=1,n rate=44100,n output=True,n frames_per_buffer=4410,n output_device_index=options.soundcard,n)nnndef get_netdev_bytes(dev):n b = int(open(/sys/class/net/%s/statistics/rx_bytes % dev).read())n b += int(open(/sys/class/net/%s/statistics/tx_bytes % dev).read())n return bnnnSAMPLE = [n (0.002, 0.15),n (0.02, 0.28), (0.04, 0.35), (0.08, 0.4), (0.12, 0.44), (0.16, 0.48),n (0.2, 0.51), (0.24, 0.54), (0.28, 0.56), (0.32, 0.59), (0.37, 0.62),n (0.4, 0.64), (0.44, 0.66), (0.48, 0.69), (0.52, 0.71), (0.56, 0.73),n (0.6, 0.76), (0.64, 0.78), (0.68, 0.8), (0.72, 0.82), (0.76, 0.84),n (0.8, 0.86), (0.84, 0.88), (0.88, 0.9), (0.92, 0.92), (0.95, 0.94), (0.96, 0.95),n (1.0, 0.97),n]nnndef percent2volume(x):n for (p, v), (pn, vn) in zip([(0.0, 0.0)] + SAMPLE, SAMPLE):n if x == p:n return vn if p < x < pn:n return (x - p) / (pn - p) * (vn - v) + vnn return vnnnnCARRIER = np.sin(np.array([440.0*2*math.pi*i/44100 for i in xrange(4410)], dtype=np.float32))nnndef get_audio_data(percentage):n return (CARRIER * (options.volume * percent2volume(options.value or percentage))).tobytes()nnnlast_t = time.time()nlast_b = get_netdev_bytes(options.dev)nlast_percentage = 0nnwhile True:n t = time.time()n b = get_netdev_bytes(options.dev)n rate = ((b - last_b) / (t - last_t)) / 1024.0 / 1024.0n if options.log_scale:n percentage = math.log(1 + rate) / math.log(1 + options.bandwidth)n else:n percentage = rate / options.bandwidthnn percentage = max(percentage, 0)n percentage = min(percentage, 1)nn percentage = options.ewma_coeff * percentage + (1-options.ewma_coeff) * last_percentagen last_percentage = percentagenn last_t = tn last_b = bn stream.write(get_audio_data(percentage))n

3. 視頻

https://www.zhihu.com/video/887513952291012608
推薦閱讀:

Python的在線學習地址?
五道有意思的題目,看看你對Python了解多少!
from yield to await——Python協程演進過程(二)

TAG:Python |