inital working commit

This commit is contained in:
2022-11-18 23:32:47 +01:00
parent 0c5ef1aad7
commit bac31b3d05
5 changed files with 686 additions and 0 deletions

136
.gitignore vendored Normal file
View File

@@ -0,0 +1,136 @@
# Created by https://www.toptal.com/developers/gitignore/api/goland+all,linux,windows
# Edit at https://www.toptal.com/developers/gitignore?templates=goland+all,linux,windows
### GoLand+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### GoLand+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/*
!.idea/codeStyles
!.idea/runConfigurations
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/goland+all,linux,windows
config.yaml

5
config.sample.yaml Normal file
View File

@@ -0,0 +1,5 @@
logging:
level: DEBUG
station_ip: "ip here"
password: "password here"

16
go.mod Normal file
View File

@@ -0,0 +1,16 @@
module VodaDocsis
go 1.19
require (
github.com/influxdata/line-protocol/v2 v2.2.1
github.com/sirupsen/logrus v1.9.0
github.com/wercker/journalhook v0.0.0-20180428041537-5d0a5ae867b3
golang.org/x/crypto v0.2.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
golang.org/x/sys v0.2.0 // indirect
)

51
go.sum Normal file
View File

@@ -0,0 +1,51 @@
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=
github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/influxdata/line-protocol-corpus v0.0.0-20210519164801-ca6fa5da0184/go.mod h1:03nmhxzZ7Xk2pdG+lmMd7mHDfeVOYFyhOgwO61qWU98=
github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937 h1:MHJNQ+p99hFATQm6ORoLmpUCF7ovjwEFshs/NHzAbig=
github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937/go.mod h1:BKR9c0uHSmRgM/se9JhFHtTT7JTO67X23MtKMHtZcpo=
github.com/influxdata/line-protocol/v2 v2.0.0-20210312151457-c52fdecb625a/go.mod h1:6+9Xt5Sq1rWx+glMgxhcg2c0DUaehK+5TDcPZ76GypY=
github.com/influxdata/line-protocol/v2 v2.1.0/go.mod h1:QKw43hdUBg3GTk2iC3iyCxksNj7PX9aUSeYOYE/ceHY=
github.com/influxdata/line-protocol/v2 v2.2.1 h1:EAPkqJ9Km4uAxtMRgUubJyqAr6zgWM0dznKMLRauQRE=
github.com/influxdata/line-protocol/v2 v2.2.1/go.mod h1:DmB3Cnh+3oxmG6LOBIxce4oaL4CPj3OmMPgvauXh+tM=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/wercker/journalhook v0.0.0-20180428041537-5d0a5ae867b3 h1:shC1HB1UogxN5Ech3Yqaaxj1X/P656PPCB4RbojIJqc=
github.com/wercker/journalhook v0.0.0-20180428041537-5d0a5ae867b3/go.mod h1:XCsSkdKK4gwBMNrOCZWww0pX6AOt+2gYc5Z6jBRrNVg=
golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE=
golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

478
main.go Normal file
View File

@@ -0,0 +1,478 @@
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"github.com/influxdata/line-protocol/v2/lineprotocol"
log "github.com/sirupsen/logrus"
"github.com/wercker/journalhook"
"golang.org/x/crypto/pbkdf2"
"gopkg.in/yaml.v3"
"io"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"os/signal"
"path"
"strconv"
"strings"
"syscall"
"time"
)
type Conf struct {
Logging struct {
Level string
}
StationIP string `yaml:"station_ip"`
Password string `yaml:"password"`
}
var (
configFlag = flag.String("config", "config.yaml", "path of config file to use")
journalLogFlag = flag.Bool("journal", false, "log to systemd journal instead of stdout/stderr")
conf = Conf{}
httpClient *http.Client
)
const (
ITERATIONS = 1000
KEYSIZEBITS = 128
USERNAME = "admin"
)
type ChannelType string
//goland:noinspection ALL
const (
OFDM ChannelType = "OFDM"
SCQAM ChannelType = "SC-QAM"
OFDMA ChannelType = "OFDMA"
)
func (c ChannelType) String() string {
return string(c)
}
type ChannelDirection string
const (
UP ChannelDirection = "UP"
DOWN ChannelDirection = "DOWN"
)
func (c ChannelDirection) String() string {
return strings.ToLower(string(c))
}
type BaseResponse struct {
Error string `json:"error"`
Message string `json:"message"`
Data json.RawMessage `json:"data"`
}
type LoginResponse struct {
Intf string `json:"intf"`
User string `json:"user"`
UID string `json:"uid"`
Dpd string `json:"Dpd"`
RemoteAddr string `json:"remoteAddr"`
UserAgent string `json:"userAgent"`
HTTPReferer string `json:"httpReferer"`
}
type DOCSISResponse struct {
OfdmDownstream []*DownstreamOFDMChannelInfo `json:"ofdm_downstream"`
Downstream []*DownstreamChannelInfo `json:"downstream"`
OfdmaUpstream []*UpstreamOFDMChannelInfo `json:"ofdma_upstream"`
Upstream []*UpstreamChannelInfo `json:"upstream"`
}
type DownstreamOFDMChannelInfo struct {
ID string `json:"__id"`
ChannelIDOfdm string `json:"channelid_ofdm"`
StartFrequency string `json:"start_frequency"`
EndFrequency string `json:"end_frequency"`
CentralFrequencyOfdm string `json:"CentralFrequency_ofdm"`
Bandwidth string `json:"bandwidth"`
PowerOfdm string `json:"power_ofdm"`
SNROfdm string `json:"SNR_ofdm"`
FFTOfdm string `json:"FFT_ofdm"`
LockedOfdm string `json:"locked_ofdm"`
ChannelType string `json:"ChannelType"`
}
type UpstreamOFDMChannelInfo struct {
ID string `json:"__id"`
ChannelIDUp string `json:"channelidup"`
StartFrequency string `json:"start_frequency"`
EndFrequency string `json:"end_frequency"`
Power string `json:"power"`
CentralFrequency string `json:"CentralFrequency"`
Bandwidth string `json:"bandwidth"`
FFT string `json:"FFT"`
ChannelType string `json:"ChannelType"`
RangingStatus string `json:"RangingStatus"`
}
type DownstreamChannelInfo struct {
ID string `json:"__id"`
ChannelID string `json:"channelid"`
CentralFrequency string `json:"CentralFrequency"`
Power string `json:"power"`
SNR string `json:"SNR"`
FFT string `json:"FFT"`
Locked string `json:"locked"`
ChannelType string `json:"ChannelType"`
}
type UpstreamChannelInfo struct {
ID string `json:"__id"`
ChannelIDUp string `json:"channelidup"`
CentralFrequency string `json:"CentralFrequency"`
Power string `json:"power"`
ChannelType string `json:"ChannelType"`
FFT string `json:"FFT"`
RangingStatus string `json:"RangingStatus"`
}
type SaltResponse struct {
Error string `json:"error"`
Salt string `json:"salt"`
SaltWebUI string `json:"saltwebui"`
}
type DOCSISChannelInfo struct {
Type ChannelType
Power float64
RangingStatus *string
SNR *float64
Direction ChannelDirection
Frequency uint64
ID string
}
func main() {
killSignals := make(chan os.Signal, 1)
signal.Notify(killSignals, syscall.SIGINT, syscall.SIGTERM)
reloadSignals := make(chan os.Signal, 1)
signal.Notify(reloadSignals, syscall.SIGUSR1)
flag.Parse()
confStr, err := os.ReadFile(*configFlag)
if err != nil {
log.Fatalf("Unable to open config: %v", err)
}
err = yaml.Unmarshal(confStr, &conf)
if err != nil {
log.Fatalf("Unable to parse config: %v", err)
}
lvl, err := log.ParseLevel(conf.Logging.Level)
if err != nil {
log.Fatalf("Failure setting logging level: %v", err)
}
log.SetLevel(lvl)
if *journalLogFlag {
journalhook.Enable()
}
cj, err := cookiejar.New(nil)
if err != nil {
log.Fatalf("error creating jar")
}
httpClient = &http.Client{
Timeout: time.Second * 30,
Jar: cj,
}
if err := login(); err != nil {
log.Fatalf("error logging in: %v", err)
}
docsisStart := time.Now()
var dData *DOCSISResponse
if dData, err = DOCSISStatus(); err != nil {
log.Errorf("error getting docsis status: %v", err)
err = logout()
if err != nil {
log.Fatalf("error logging out: %v", err)
}
return
}
docsisTime := time.Now()
var enc lineprotocol.Encoder
enc.SetPrecision(lineprotocol.Microsecond)
enc.StartLine("docsis_diagnostic")
enc.AddField("response_time_ms", lineprotocol.MustNewValue(time.Since(docsisStart).Milliseconds()))
enc.EndLine(docsisTime)
for _, channel := range transformDOCSIS(dData) {
enc.StartLine("docsis")
enc.AddTag("channel_id", channel.ID)
enc.AddTag("direction", channel.Direction.String())
enc.AddTag("type", channel.Type.String())
enc.AddField("frequency", lineprotocol.MustNewValue(channel.Frequency))
enc.AddField("power", lineprotocol.MustNewValue(channel.Power))
if channel.Direction == UP {
enc.AddField("ranging_status", lineprotocol.MustNewValue(*channel.RangingStatus))
} else {
enc.AddField("snr", lineprotocol.MustNewValue(*channel.SNR))
}
enc.EndLine(docsisTime)
}
if err = enc.Err(); err != nil {
log.Fatalf("influx line protocol encoding error: %v", err)
}
fmt.Printf("%s", enc.Bytes())
if err = logout(); err != nil {
log.Fatalf("error logging out: %v", err)
}
}
func login() error {
postData := url.Values{}
postData.Add("username", USERNAME)
postData.Add("password", "seeksalthash")
postData.Add("logout", "true")
req, err := http.NewRequest(http.MethodPost, "http://"+path.Join(conf.StationIP, "/api/v1/session/login"), strings.NewReader(postData.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
req.Header.Set("Referer", "http://"+conf.StationIP+"/")
req.Header.Set("X-Requested-With", "XMLHttpRequest")
sResp, err := httpClient.Do(req)
if err != nil {
return err
}
if sResp.StatusCode != 200 {
return fmt.Errorf("login failed")
}
rData, err := io.ReadAll(sResp.Body)
if err != nil {
return err
}
err = sResp.Body.Close()
if err != nil {
return err
}
saltResp := new(SaltResponse)
if err = json.Unmarshal(rData, saltResp); err != nil {
return err
}
if saltResp.Error != "ok" {
return fmt.Errorf("login failed: %+v", saltResp.Error)
}
finalKey := Key(Key(conf.Password, saltResp.Salt), saltResp.SaltWebUI)
postData = url.Values{}
postData.Add("username", USERNAME)
postData.Add("password", finalKey)
nResp, err := APIRequest(http.MethodPost, "/api/v1/session/login", postData)
if err != nil {
return err
}
loginData := new(LoginResponse)
if err = json.Unmarshal(nResp.Data, loginData); err != nil {
return err
}
_, err = APIRequest(http.MethodGet, "/api/v1/session/menu", nil)
if err != nil {
return err
}
return nil
}
func DOCSISStatus() (*DOCSISResponse, error) {
resp, err := APIRequest(http.MethodGet, "/api/v1/sta_docsis_status", nil)
if err != nil {
return nil, err
}
docsisData := new(DOCSISResponse)
if err = json.Unmarshal(resp.Data, docsisData); err != nil {
return nil, err
}
log.Debugf("docsis response: %+v", docsisData)
return docsisData, nil
}
func logout() error {
_, err := APIRequest(http.MethodPost, "/api/v1/session/logout", nil)
if err != nil {
return err
}
return nil
}
func Key(pw string, salt string) string {
return hex.EncodeToString(pbkdf2.Key([]byte(pw), []byte(salt), ITERATIONS, KEYSIZEBITS/8, sha256.New))
}
func APIRequest(method string, endpoint string, postData url.Values) (*BaseResponse, error) {
log.Debugf("[API] %s: %s", method, endpoint)
var bodyReader io.Reader
if postData != nil {
bodyReader = strings.NewReader(postData.Encode())
}
req, err := http.NewRequest(method, "http://"+path.Join(conf.StationIP, endpoint), bodyReader)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
req.Header.Set("Referer", "http://"+conf.StationIP+"/")
req.Header.Set("X-Requested-With", "XMLHttpRequest")
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
rData, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = resp.Body.Close()
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("[API] calling %s failed with %d: %s", endpoint, resp.StatusCode, string(rData))
}
baseResp := new(BaseResponse)
if err = json.Unmarshal(rData, baseResp); err != nil {
return nil, err
}
if baseResp.Error != "ok" {
var data any
_ = json.Unmarshal(baseResp.Data, &data)
return nil, fmt.Errorf("%s failed: %s (data: %+v)", endpoint, baseResp.Message, data)
}
return baseResp, nil
}
func transformDOCSIS(rawDOCSIS *DOCSISResponse) (nChannels []*DOCSISChannelInfo) {
for _, channel := range rawDOCSIS.OfdmDownstream {
nChannels = append(nChannels, &DOCSISChannelInfo{
Type: ChannelType(channel.ChannelType),
Power: powerStr2Power(channel.PowerOfdm),
SNR: snrStr2SNR(channel.SNROfdm),
Direction: DOWN,
Frequency: freqStr2Hz(channel.CentralFrequencyOfdm),
ID: channel.ChannelIDOfdm,
})
}
for _, channel := range rawDOCSIS.Downstream {
nChannels = append(nChannels, &DOCSISChannelInfo{
Type: ChannelType(channel.ChannelType),
Power: powerStr2Power(channel.Power),
SNR: snrStr2SNR(channel.SNR),
Direction: DOWN,
Frequency: freqStr2Hz(channel.CentralFrequency),
ID: channel.ChannelID,
})
}
for _, channel := range rawDOCSIS.OfdmaUpstream {
nChannels = append(nChannels, &DOCSISChannelInfo{
Type: ChannelType(channel.ChannelType),
Power: powerStr2Power(channel.Power),
RangingStatus: &channel.RangingStatus,
Direction: UP,
Frequency: freqStr2Hz(channel.CentralFrequency),
ID: channel.ChannelIDUp,
})
}
for _, channel := range rawDOCSIS.Upstream {
nChannels = append(nChannels, &DOCSISChannelInfo{
Type: ChannelType(channel.ChannelType),
Power: powerStr2Power(channel.Power),
RangingStatus: &channel.RangingStatus,
Direction: UP,
Frequency: freqStr2Hz(channel.CentralFrequency),
ID: channel.ChannelIDUp,
})
}
return
}
func snrStr2SNR(snrStr string) *float64 {
rSNR, unit := strUnit2Elements(snrStr)
if strings.ToLower(unit) != "db" {
panic(fmt.Sprintf("error parsing power unit %s", snrStr))
}
return &rSNR
}
func powerStr2Power(powerStr string) float64 {
rPower, unit := strUnit2Elements(powerStr)
if strings.ToLower(unit) != "dbmv" {
panic(fmt.Sprintf("error parsing power unit %s", powerStr))
}
return rPower
}
func freqStr2Hz(freqStr string) uint64 {
rFreq, unit := strUnit2Elements(freqStr)
if strings.ToLower(unit) != "mhz" {
panic(fmt.Sprintf("error parsing frequency unit %s", freqStr))
}
return uint64(rFreq * 1000000)
}
func strUnit2Elements(rawStr string) (float64, string) {
splitStr := strings.Split(rawStr, " ")
if len(splitStr) < 2 {
panic(fmt.Sprintf("error parsing floatUnit %s", rawStr))
}
rFloat, err := strconv.ParseFloat(splitStr[0], 64)
if err != nil {
panic(fmt.Sprintf("error parsing floatUnit %s", splitStr[0]))
}
return rFloat, splitStr[1]
}