├── .gitignore ├── .golangci.yml ├── .snapcraft └── travis_snapcraft.cfg ├── .travis.yml ├── BuildConfig.go ├── DSP ├── DSP.go ├── Decoder.go ├── Globals.go ├── Parameters.go └── Tools.go ├── Demuxer ├── BaseModels.go ├── DirectDemuxer.go ├── FileDemuxer.go ├── TCPServer.go └── UDPServer.go ├── Display ├── Color.go ├── ConsoleWritter.go └── Display.go ├── Frontend ├── AirspyDevice │ ├── AirspyDevice.cpp │ ├── AirspyDevice.go │ ├── AirspyDevice.h │ ├── AirspyDevice.i │ ├── AirspyDevice_wrap.cxx │ └── AirspyDevice_wrap.h ├── AirspyFrontend.go ├── BaseFrontend.go ├── CFileFrontend.go ├── DeviceParameters.h ├── LimedrvFrontend.go ├── RTLSDRDevice │ ├── RTLSDRDevice.go │ ├── RTLSDRDevice.i │ ├── RTLSDRDevice_wrap.cxx │ ├── RTLSDRDevice_wrap.h │ ├── RtlFrontend.cpp │ └── RtlFrontend.h ├── RTLSDRFrontend.go └── SpyserverFrontend.go ├── ImageProcessor ├── GOESABI.go ├── ImageData │ ├── README.md │ ├── bindata.go │ ├── etc.go │ ├── files │ │ ├── FreeMono.ttf │ │ ├── ne_50m_admin_0_countries.dbf │ │ ├── ne_50m_admin_0_countries.prj │ │ ├── ne_50m_admin_0_countries.shp │ │ ├── ne_50m_admin_0_countries.shx │ │ ├── ne_50m_admin_1_states_provinces.dbf │ │ ├── ne_50m_admin_1_states_provinces.prj │ │ ├── ne_50m_admin_1_states_provinces.shp │ │ ├── ne_50m_admin_1_states_provinces.shx │ │ └── wx-star.com_GOES-R_ABI_False-Color-LUT.png │ └── temperatureScale.go ├── ImageProcessor.go ├── ImageTools │ ├── ColorReader.go │ ├── CurveManipulator.go │ ├── Enhancer.go │ ├── Lut1D.go │ ├── Lut2D.go │ ├── MultiSegmentDump.go │ └── tools.go ├── MapCutter │ └── MapCutter.go ├── MapDrawer │ └── MapDrawer.go ├── PlainLRITImage.go ├── Projector │ ├── LinearProjectionConverter.go │ ├── Projection.go │ ├── Projector.go │ └── Sampler.go └── Structs │ └── MultiSegmentImage.go ├── LICENSE ├── Logger └── LogManager.go ├── Makefile ├── Models ├── Config.go └── Statistics.go ├── README.md ├── RPC ├── prepare.sh ├── proto │ └── main.proto ├── rpcserver.go ├── sathelperapp │ └── main.pb.go └── servers │ └── information.go ├── Tools └── tools.go ├── XRIT ├── FileParser.go ├── Geo │ ├── GeoConverter.go │ └── Tools.go ├── NOAAProductID │ └── NOAAProductId.go ├── PacketData │ ├── CompressionType.go │ ├── FileTypeCode.go │ ├── HeaderType.go │ ├── NOAAProduct.go │ └── NOAASubProduct.go ├── Presets │ └── noaaProducts.go ├── ScannerSubProduct │ └── SubProductID.go ├── Structs │ ├── BaseRecord.go │ ├── DCSPacket.go │ ├── ImageNavigationRecord.go │ ├── ImageStructureRecord.go │ ├── NOAASpecificRecord.go │ ├── PrimaryRecord.go │ ├── RiceCompressionRecord.go │ ├── SegmentIdentificationRecord.go │ ├── StringFieldRecord.go │ ├── TimestampRecord.go │ └── UnknownHeader.go ├── VCID2Name.go └── XRITHeader.go ├── ccsds ├── FileAssembler.go ├── FileHandlers.go ├── MSDU.go ├── MSDUInfo.go ├── SequenceType.go ├── VCDU.go ├── parser.go └── transportParser.go ├── cmd ├── MultiSegmentDump │ ├── multiSegmentDump.go │ └── testdata │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg000 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg001 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg002 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg003 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg004 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg005 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg006 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg007 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg008 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg009 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg010 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg011 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg012 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg013 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg014 │ │ ├── OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg015 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg000 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg001 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg002 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg003 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg004 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg005 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg006 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg007 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg008 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg009 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg010 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg011 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg012 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg013 │ │ ├── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg014 │ │ └── OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg015 ├── SatHelperApp │ ├── BaseRPCSource.go │ ├── Config.go │ ├── SatHelperApp.go │ └── Tools.go ├── demuxReplay │ ├── .gitignore │ └── replay.go ├── rpcClient │ └── client.go ├── xritcat │ └── xritcat.go ├── xritimg │ └── xritimg.go ├── xritparse │ ├── printers.go │ └── xritparse.go └── xritpdcs │ └── xritpdcs.go ├── go.mod ├── go.sum ├── metrics └── metrics.go ├── snap └── snapcraft.yaml ├── travis-build.sh └── version /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | .vscode 4 | .gopath 5 | SatHelperApp.cfg 6 | parts 7 | prime 8 | stage 9 | tmp 10 | *.snap 11 | snap/.snapcraft 12 | build 13 | dist 14 | sathelperapp_source.tar.bz2 15 | demuxdump* 16 | *.zip 17 | cmd/SatHelperApp/SatHelperApp 18 | cmd/SatHelperApp/tmp/ 19 | cmd/SatHelperApp/out/ 20 | SatHelperApp 21 | cmd/xritparse/xritparse 22 | cmd/demuxReplay/replay 23 | release* 24 | bins 25 | zips 26 | cmd/xritpdcs/xritpdcs 27 | cmd/rpcClient/client 28 | out 29 | *.exe 30 | cmd/MultiSegmentDump/testdata/*.json 31 | cmd/MultiSegmentDump/testdata/*.png 32 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # This file contains all available configuration options 2 | # with their default values. 3 | 4 | # options for analysis running 5 | run: 6 | 7 | # timeout for analysis, e.g. 30s, 5m, default is 1m 8 | deadline: 1m 9 | 10 | # exit code when at least one issue was found, default is 1 11 | issues-exit-code: 1 12 | modules-download-mode: readonly 13 | 14 | skip-files: # SWIG Auto-generated files 15 | - Frontend/AirspyDevice/AirspyDevice.go 16 | - Frontend/LimeDevice/LimeDevice.go 17 | - Frontend/SpyserverDevice/SpyserverDevice.go 18 | - Frontend/RTLSDRDevice/RTLSDRDevice.go 19 | skip-dirs: # Snap and temp folders 20 | - snap 21 | - parts 22 | - stage 23 | - tmp 24 | - prime 25 | -------------------------------------------------------------------------------- /.snapcraft/travis_snapcraft.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/.snapcraft/travis_snapcraft.cfg -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | env: 3 | global: 4 | - COMMIT=${TRAVIS_COMMIT::8} 5 | - GO111MODULE=on 6 | 7 | matrix: 8 | include: 9 | - language: go 10 | go: 11 | - 1.11.x 12 | git: 13 | depth: 1 14 | before_install: 15 | - sudo add-apt-repository ppa:opensatelliteproject/ppa -y 16 | - sudo add-apt-repository ppa:opensatelliteproject/drivers -y 17 | - sudo add-apt-repository ppa:myriadrf/gnuradio -y 18 | - sudo apt-get -qq update 19 | - sudo apt-get install -y libaec-dev libaec0 libcorrect libsathelper libsoapysdr0.6 libairspy0 libsoapysdr-dev libairspy-dev git g++ cmake libsqlite3-dev libi2c-dev libusb-1.0-0-dev 20 | - echo "Building RTLSDR" 21 | - git clone https://github.com/librtlsdr/librtlsdr.git 22 | - cd librtlsdr 23 | - mkdir build && cd build 24 | - cmake .. 25 | - make -j10 26 | - sudo make install 27 | - sudo ldconfig 28 | - cd ../.. 29 | - echo "Building Static LimeSuite" 30 | - git clone https://github.com/myriadrf/LimeSuite.git 31 | - cd LimeSuite 32 | - git checkout stable 33 | - mkdir builddir && cd builddir 34 | - cmake ../ 35 | - make -j10 36 | - sudo make install 37 | - sudo ldconfig 38 | - cd ../.. 39 | - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.15.0 40 | script: 41 | - go get 42 | - make test 43 | - golangci-lint run 44 | - ./travis-build.sh 45 | deploy: 46 | - provider: releases 47 | api_key: 48 | secure: cID0Ex92EsQRN6Xsh1ngF9jZVqiVdYCNC2JhnEbnTtipb5Z00m8wasdo3R22aafGUkaWpcg0zbRRSC/VCMqrLj6PRoKJ0KBtOQA4BiJ3xc4YkWg2nmwOs19KSn61vO6VIrIDfQfBSlavfTsUdLKc0tE2QGxkw2j49Y8+0vswynXvx9yQ+renigtS8yhK6FT6VpIQ1+Yqx0DSN7XnFyAlqA1fymx32GzXIFhs1llOUiUPmnyOryJFfJxus8GGf1f8QX+elgBEN08nYkvc2yGlhh/+LhY7SBcdKgR3acHvhooaxmuPXfC92OVlrizV27wLsTX913jT7ELm5kGqEP7FuBpE4kvxoZhcZI71vt5P3CR60ELz9vTfN+U2YTnaZ43Nlgjwzq9SfQPxXBF2FLGEEBd4zkuEKGvATYLrI1WdPo1Zns85viFWo3X65eZ/WW2tK+m5wVYQmMWw8RYc/ANrVdovzX6Ss+Z2hTh9qvGNjy1Ph+qvaVU9cdeO8JM9r9+Xsm4UTGZ7nUEnYN5DbeZ7aDGk4p8E67B19Xvroyzt/JEIrfH3C+y+eX1YZOS74xi9REeFzje90SODBh18onsI6sfssw+AHE7H24hMfHadjUWqMzx/5vgCi/pyP13inuwasCrqS13XJEvlY3elwhdFVbb8OkwVW99Ukkzn+rq5tVE= 49 | file_glob: true 50 | file: zips/* 51 | skip_cleanup: true 52 | on: 53 | tags: true 54 | repo: opensatelliteproject/SatHelperApp 55 | - language: bash 56 | addons: 57 | snaps: 58 | - name: snapcraft 59 | classic: true 60 | services: [docker] 61 | script: 62 | - sudo apt-get -qq update && sudo apt-get install libusb-1.0-0 swig swig3.0 63 | - docker run --rm -v "$PWD":/build -w /build snapcore/snapcraft bash -c "apt update && snapcraft" 64 | - ls -lah 65 | deploy: 66 | - provider: snap 67 | snap: sathelperapp*.snap 68 | channel: edge 69 | skip_cleanup: true 70 | on: 71 | tags: true 72 | repo: opensatelliteproject/SatHelperApp 73 | after_success: 74 | - openssl aes-256-cbc -K $encrypted_f24dcc1fa9db_key -iv $encrypted_f24dcc1fa9db_iv -in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d -------------------------------------------------------------------------------- /BuildConfig.go: -------------------------------------------------------------------------------- 1 | package SatHelperApp 2 | 3 | var ( 4 | VersionString string 5 | RevString string 6 | CompilationTime string 7 | CompilationDate string 8 | ) 9 | 10 | func GetVersion() string { 11 | if VersionString == "" { 12 | VersionString = "" 13 | } 14 | 15 | return VersionString 16 | } 17 | 18 | func GetRevision() string { 19 | if RevString == "" { 20 | RevString = "" 21 | } 22 | 23 | return RevString 24 | } 25 | 26 | func GetCompilationTime() string { 27 | if CompilationTime == "" { 28 | CompilationTime = "" 29 | } 30 | 31 | return CompilationTime 32 | } 33 | 34 | func GetCompilationDate() string { 35 | if CompilationDate == "" { 36 | CompilationDate = "" 37 | } 38 | 39 | return CompilationDate 40 | } 41 | -------------------------------------------------------------------------------- /DSP/Globals.go: -------------------------------------------------------------------------------- 1 | package DSP 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/Demuxer" 5 | "github.com/opensatelliteproject/SatHelperApp/Frontend" 6 | "github.com/opensatelliteproject/SatHelperApp/Models" 7 | "github.com/opensatelliteproject/libsathelper" 8 | "github.com/racerxdl/go.fifo" 9 | "github.com/racerxdl/segdsp/dsp" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | // region Global Globals 15 | var running = false 16 | 17 | // endregion 18 | // region DSP Globals 19 | var samplesFifo *fifo.Queue 20 | var buffer0 []complex64 21 | var buffer1 []complex64 22 | 23 | //var decimator SatHelper.FirFilter 24 | var agc SatHelper.AGC 25 | 26 | //var rrcFilter SatHelper.FirFilter 27 | //var costasLoop SatHelper.CostasLoop 28 | var clockRecovery SatHelper.ClockRecovery 29 | var Device Frontend.BaseFrontend 30 | 31 | // region SegDSP Blocks 32 | var agcNew *dsp.SimpleAGC 33 | var rrcFilterNew *dsp.FirFilter 34 | var decimatorNew *dsp.FirFilter 35 | var costasLoopNew dsp.CostasLoop 36 | 37 | // endregion 38 | 39 | // endregion 40 | // region Decoder Globals 41 | var symbolsFifo *fifo.Queue 42 | var constellationFifo *fifo.Queue 43 | 44 | var viterbiData []byte 45 | var decodedData []byte 46 | var lastFrameEnd []byte 47 | 48 | var codedData []byte 49 | var rsCorrectedData []byte 50 | var rsWorkBuffer []byte 51 | 52 | var syncWord []byte 53 | 54 | var viterbi SatHelper.Viterbi27 55 | var reedSolomon SatHelper.ReedSolomon 56 | var correlator SatHelper.Correlator 57 | var packetFixer SatHelper.PacketFixer 58 | 59 | var statistics Models.Statistics 60 | var statisticsMutex = &sync.Mutex{} 61 | 62 | var ConstellationServer *Demuxer.UDPServer 63 | 64 | var SDemuxer Demuxer.BaseDemuxer 65 | var StatisticsServer *Demuxer.TCPServer 66 | 67 | var demodFifoUsage uint8 68 | var decodFifoUsage uint8 69 | 70 | var lastConstellationSend time.Time 71 | var constellationBuffer []byte 72 | 73 | var startTime uint32 74 | 75 | // endregion 76 | 77 | func GetStats() Models.Statistics { 78 | statisticsMutex.Lock() 79 | stat := statistics 80 | statisticsMutex.Unlock() 81 | return stat 82 | } 83 | 84 | func SetStats(stat Models.Statistics) { 85 | statisticsMutex.Lock() 86 | statistics = stat 87 | statisticsMutex.Unlock() 88 | } 89 | -------------------------------------------------------------------------------- /DSP/Parameters.go: -------------------------------------------------------------------------------- 1 | package DSP 2 | 3 | import ( 4 | . "github.com/opensatelliteproject/SatHelperApp/Models" 5 | ) 6 | 7 | // region Demodulator Parameters 8 | // These are the parameters used by the demodulator. Change with care. 9 | 10 | // GOES HRIT Settings 11 | const HritCenterFrequency = 1694100000 12 | const HritSymbolRate = 927000 13 | const HritRrcAlpha float32 = 0.3 14 | 15 | // GOES LRIT Settings 16 | const LritCenterFrequency = 1691000000 17 | const LritSymbolRate = 293883 18 | const LritRrcAlpha = 0.5 19 | 20 | // Loop Settings 21 | const LoopOrder = 2 22 | const RrcTaps = 31 23 | const PllAlpha float32 = 0.001 24 | const ClockAlpha float32 = 0.0037 25 | const ClockMu float32 = 0.5 26 | const ClockOmegaLimit float32 = 0.005 27 | const ClockGainOmega = (ClockAlpha * ClockAlpha) / 4.0 28 | const AgcRate float32 = 0.01 29 | const AgcReference float32 = 0.5 30 | const AgcGain float32 = 1.0 31 | const AgcMaxGain float32 = 4000 32 | 33 | const AirspyMiniDefaultSamplerate = 3000000 34 | 35 | //const AirspyR2DefaultSamplerate = 2500000 36 | const DefaultSampleRate = AirspyMiniDefaultSamplerate 37 | const DefaultDecimation = 1 38 | 39 | //const DefaultDeviceNumber = 0 40 | 41 | const DefaultLnaGain = 5 42 | const DefaultVgaGain = 5 43 | const DefaultMixGain = 5 44 | 45 | const DefaultBiast = false 46 | 47 | // FIFO Size in Samples 48 | // 10 * 1024 * 1024 samples is about 40Mb of ram. 49 | // This should be more than enough 50 | const FifoSize = 10 * 1024 * 1024 51 | 52 | // endregion 53 | // region Decoder Parameters 54 | 55 | const HritUw0 uint64 = 0xfc4ef4fd0cc2df89 56 | const HritUw2 uint64 = 0x25010b02f33d2076 57 | const LritUw0 uint64 = 0xfca2b63db00d9794 58 | const LritUw2 uint64 = 0x035d49c24ff2686b 59 | 60 | const SyncWordSize = 4 61 | const FrameSize = 1024 62 | const FrameBits = FrameSize * 8 63 | const CodedFrameSize = FrameBits * 2 64 | const MinCorrelationBits = 46 65 | const RsBlocks = 4 66 | const RsParitySize = 32 67 | const RsParityBlockSize = RsParitySize * RsBlocks 68 | const LastFrameDataBits = 64 69 | const LastFrameData = LastFrameDataBits / 8 70 | 71 | const DefaultFlywheelRecheck = 100 72 | const DefaultVchannelPort = 5001 73 | const DefaultStatisticsPort = 5002 74 | const DefaultRPCPort = 5500 75 | const DefaultPrometheusPort = 9100 76 | 77 | const AverageLastNSamples = 10000 78 | 79 | // endregion 80 | // region Current Config Stuff 81 | var CurrentConfig AppConfig 82 | 83 | func SetHRITMode() { 84 | // HRIT Mode 85 | CurrentConfig.Base.SymbolRate = HritSymbolRate 86 | CurrentConfig.Base.Mode = "hrit" 87 | CurrentConfig.Base.RRCAlpha = HritRrcAlpha 88 | CurrentConfig.Source.Frequency = HritCenterFrequency 89 | } 90 | 91 | func SetLRITMode() { 92 | // LRIT Mode 93 | CurrentConfig.Base.SymbolRate = LritSymbolRate 94 | CurrentConfig.Base.Mode = "lrit" 95 | CurrentConfig.Base.RRCAlpha = LritRrcAlpha 96 | CurrentConfig.Source.Frequency = LritCenterFrequency 97 | } 98 | 99 | // endregion 100 | -------------------------------------------------------------------------------- /DSP/Tools.go: -------------------------------------------------------------------------------- 1 | package DSP 2 | 3 | import "log" 4 | import "github.com/racerxdl/go.fifo" 5 | 6 | func AddToFifoC64(fifo *fifo.Queue, arr []complex64, length int) { 7 | fifo.UnsafeLock() 8 | defer fifo.UnsafeUnlock() 9 | for i := 0; i < length; i++ { 10 | if fifo.UnsafeLen() >= FifoSize { 11 | log.Printf("FIFO Overflowing!!") 12 | break 13 | } 14 | fifo.UnsafeAdd(arr[i]) 15 | } 16 | } 17 | 18 | func AddToFifoS16toC64(fifo *fifo.Queue, arr []int16, length int) { 19 | fifo.UnsafeLock() 20 | defer fifo.UnsafeUnlock() 21 | for i := 0; i < length; i++ { 22 | if fifo.UnsafeLen() >= FifoSize { 23 | log.Printf("FIFO Overflowing!!") 24 | break 25 | } 26 | 27 | var c = complex(float32(arr[i*2])/32768.0, float32(arr[i*2+1])/32768.0) 28 | fifo.UnsafeAdd(c) 29 | } 30 | } 31 | 32 | func AddToFifoS8toC64(fifo *fifo.Queue, arr []int8, length int) { 33 | fifo.UnsafeLock() 34 | defer fifo.UnsafeUnlock() 35 | for i := 0; i < length; i++ { 36 | if fifo.UnsafeLen() >= FifoSize { 37 | log.Printf("FIFO Overflowing!!") 38 | break 39 | } 40 | var c = complex(float32(arr[i*2])/128, float32(arr[i*2+1])/128) 41 | fifo.UnsafeAdd(c) 42 | } 43 | } 44 | 45 | func swapAndTrimSlices(a *[]complex64, b *[]complex64, length int) { 46 | *a = (*a)[:length] 47 | *b = (*b)[:length] 48 | 49 | c := *b 50 | *b = *a 51 | *a = c 52 | } 53 | 54 | func checkAndResizeBuffers(length int) { 55 | if len(buffer0) < length { 56 | buffer0 = make([]complex64, length) 57 | } 58 | if len(buffer1) < length { 59 | buffer1 = make([]complex64, length) 60 | } 61 | } 62 | 63 | func shiftWithConstantSize(arr *[]byte, pos int, length int) { 64 | for i := 0; i < length-pos; i++ { 65 | (*arr)[i] = (*arr)[pos+i] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Demuxer/BaseModels.go: -------------------------------------------------------------------------------- 1 | package Demuxer 2 | 3 | type BaseDemuxer interface { 4 | Init() 5 | Start() 6 | Stop() 7 | SendData([]byte) 8 | GetName() string 9 | } 10 | -------------------------------------------------------------------------------- /Demuxer/DirectDemuxer.go: -------------------------------------------------------------------------------- 1 | package Demuxer 2 | 3 | import ( 4 | "github.com/logrusorgru/aurora" 5 | "github.com/opensatelliteproject/SatHelperApp/Logger" 6 | "github.com/opensatelliteproject/SatHelperApp/ccsds" 7 | ) 8 | 9 | type DirectDemuxer struct { 10 | demux *ccsds.Demuxer 11 | } 12 | 13 | func MakeDirectDemuxer(outFolder, tmpFolder string, drawMap, reproject, falseColor, metaframe, enhance bool) *DirectDemuxer { 14 | d := &DirectDemuxer{} 15 | 16 | d.demux = ccsds.MakeDemuxer() 17 | d.demux.SetOutputFolder(outFolder) 18 | d.demux.SetTemporaryFolder(tmpFolder) 19 | d.demux.SetDrawMap(drawMap) 20 | d.demux.SetReprojectImage(reproject) 21 | d.demux.SetFalseColor(falseColor) 22 | d.demux.SetMetaFrame(metaframe) 23 | d.demux.SetEnhance(enhance) 24 | 25 | SLog.Info("Starting direct Demuxer with: ") 26 | SLog.Info(" Output Folder: %s", aurora.Bold(outFolder).Green()) 27 | SLog.Info(" Temporary Folder: %s", aurora.Bold(tmpFolder).Green()) 28 | 29 | d.demux.SetOnFrameLost(func(channelId, currentFrame, lastFrame int) { 30 | SLog.Info("Lost Frames for channel %d: %d", channelId, currentFrame-lastFrame-1) 31 | }) 32 | 33 | d.demux.SetOnNewVCID(func(channelId int) { 34 | SLog.Debug("New Channel: %d", channelId) 35 | }) 36 | return d 37 | } 38 | 39 | func (dd *DirectDemuxer) AddSkipVCID(vcid int) { 40 | SLog.Info("Adding VCID %d to skip list.", vcid) 41 | dd.demux.AddSkipVCID(vcid) 42 | } 43 | 44 | func (dd *DirectDemuxer) Init() {} 45 | func (dd *DirectDemuxer) Start() {} 46 | func (dd *DirectDemuxer) Stop() {} 47 | func (dd *DirectDemuxer) SendData(data []byte) { 48 | dd.demux.WriteBytes(data) 49 | } 50 | func (dd *DirectDemuxer) GetName() string { 51 | return "Direct" 52 | } 53 | -------------------------------------------------------------------------------- /Demuxer/FileDemuxer.go: -------------------------------------------------------------------------------- 1 | package Demuxer 2 | 3 | import ( 4 | "container/list" 5 | "os" 6 | "sync" 7 | ) 8 | 9 | // region Struct Definition 10 | type FileDemuxer struct { 11 | filename string 12 | clients *list.List 13 | syncMtx *sync.Mutex 14 | running bool 15 | handle *os.File 16 | } 17 | 18 | // endregion 19 | // region Constructor 20 | func NewFileDemuxer(filename string) *FileDemuxer { 21 | return &FileDemuxer{ 22 | filename: filename, 23 | syncMtx: &sync.Mutex{}, 24 | } 25 | } 26 | 27 | // endregion 28 | // region BaseDemuxer Methods 29 | func (f *FileDemuxer) Init() { 30 | f.clients = list.New() 31 | } 32 | func (f *FileDemuxer) Start() { 33 | f.running = true 34 | var err error 35 | f.handle, err = os.Create(f.filename) 36 | 37 | if err != nil { 38 | panic(err) 39 | } 40 | } 41 | func (f *FileDemuxer) Stop() { 42 | f.running = false 43 | if f.handle != nil { 44 | f.handle.Close() 45 | f.handle = nil 46 | } 47 | } 48 | func (f *FileDemuxer) SendData(frame []byte) { 49 | _, err := f.handle.Write(frame) 50 | if err != nil { 51 | panic(err) 52 | } 53 | } 54 | func (f *FileDemuxer) GetName() string { 55 | return "File" 56 | } 57 | 58 | // endregion 59 | -------------------------------------------------------------------------------- /Demuxer/TCPServer.go: -------------------------------------------------------------------------------- 1 | package Demuxer 2 | 3 | import ( 4 | "container/list" 5 | "fmt" 6 | "github.com/opensatelliteproject/SatHelperApp/Logger" 7 | "github.com/prometheus/common/log" 8 | "net" 9 | "sync" 10 | ) 11 | 12 | // region Struct Definition 13 | type TCPServer struct { 14 | port int 15 | host string 16 | clients *list.List 17 | syncMtx *sync.Mutex 18 | running bool 19 | } 20 | 21 | // endregion 22 | // region Constructor 23 | func NewTCPServer(host string, port int) *TCPServer { 24 | var tcp = &TCPServer{ 25 | port: port, 26 | host: host, 27 | syncMtx: &sync.Mutex{}, 28 | } 29 | 30 | tcp.Init() 31 | 32 | return tcp 33 | } 34 | 35 | // endregion 36 | // region BaseDemuxer Methods 37 | func (f *TCPServer) Init() { 38 | f.syncMtx.Lock() 39 | f.clients = list.New() 40 | f.syncMtx.Unlock() 41 | } 42 | func (f *TCPServer) Start() { 43 | f.syncMtx.Lock() 44 | f.running = true 45 | go f.loop() 46 | f.syncMtx.Unlock() 47 | } 48 | func (f *TCPServer) Stop() { 49 | f.syncMtx.Lock() 50 | f.running = false 51 | f.syncMtx.Unlock() 52 | } 53 | func (f *TCPServer) SendData(data []byte) { 54 | go func() { 55 | f.syncMtx.Lock() 56 | var next *list.Element 57 | for e := f.clients.Front(); e != nil; e = next { 58 | client := e.Value.(net.Conn) 59 | n, err := client.Write(data) 60 | if n != len(data) || err != nil { 61 | SLog.Error("%s", err) 62 | next = e.Next() 63 | f.clients.Remove(e) 64 | SLog.Info("Client disconnected %s", client.RemoteAddr()) 65 | } 66 | } 67 | f.syncMtx.Unlock() 68 | }() 69 | } 70 | func (f *TCPServer) GetName() string { 71 | return "TCP Server" 72 | } 73 | 74 | func (f *TCPServer) isRunning() bool { 75 | f.syncMtx.Lock() 76 | defer f.syncMtx.Unlock() 77 | return f.running 78 | } 79 | 80 | // endregion 81 | // region Loop Function 82 | func (f *TCPServer) loop() { 83 | ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", f.host, f.port)) 84 | if err != nil { 85 | SLog.Error("Error opening TCP Server Socket: %s\n", err) 86 | return 87 | } 88 | for f.isRunning() { 89 | conn, err := ln.Accept() 90 | if err != nil { 91 | log.Error(err) 92 | } else { 93 | f.syncMtx.Lock() 94 | f.clients.PushBack(conn) 95 | f.syncMtx.Unlock() 96 | SLog.Info("Client connected from %s", conn.RemoteAddr()) 97 | } 98 | } 99 | 100 | } 101 | 102 | // endregion 103 | -------------------------------------------------------------------------------- /Demuxer/UDPServer.go: -------------------------------------------------------------------------------- 1 | package Demuxer 2 | 3 | import ( 4 | "container/list" 5 | "fmt" 6 | "github.com/opensatelliteproject/SatHelperApp/Logger" 7 | "net" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | // region Struct Definition 13 | type UDPServer struct { 14 | port int 15 | host string 16 | clients *list.List 17 | syncMtx *sync.Mutex 18 | running bool 19 | conn *net.UDPConn 20 | target *net.UDPAddr 21 | } 22 | 23 | // endregion 24 | // region Constructor 25 | func NewUDPServer(host string, port int) *UDPServer { 26 | var udp = &UDPServer{ 27 | port: port, 28 | host: host, 29 | syncMtx: &sync.Mutex{}, 30 | } 31 | 32 | udp.Init() 33 | 34 | return udp 35 | } 36 | 37 | // endregion 38 | // region BaseDemuxer Methods 39 | func (f *UDPServer) Init() { 40 | f.clients = list.New() 41 | } 42 | func (f *UDPServer) Start() { 43 | f.syncMtx.Lock() 44 | f.running = true 45 | go f.loop() 46 | f.syncMtx.Unlock() 47 | } 48 | func (f *UDPServer) Stop() { 49 | f.syncMtx.Lock() 50 | f.running = false 51 | f.syncMtx.Unlock() 52 | } 53 | func (f *UDPServer) SendData(data []byte) { 54 | go func() { 55 | f.syncMtx.Lock() 56 | if f.conn != nil { 57 | _, err := f.conn.WriteToUDP(data, f.target) 58 | if err != nil { 59 | SLog.Error("Error sending payload to client: %s", err) 60 | } 61 | } 62 | f.syncMtx.Unlock() 63 | }() 64 | } 65 | func (f *UDPServer) GetName() string { 66 | return "UDP Server" 67 | } 68 | 69 | func (f *UDPServer) isRunning() bool { 70 | f.syncMtx.Lock() 71 | defer f.syncMtx.Unlock() 72 | return f.running 73 | } 74 | 75 | // endregion 76 | // region Loop Function 77 | func (f *UDPServer) loop() { 78 | f.syncMtx.Lock() 79 | SLog.Info("Starting UDP Server at port %d", f.port) 80 | serverAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", 9001)) 81 | 82 | if err != nil { 83 | SLog.Error("Error opening UDP Server Socket: %s\n", err) 84 | f.syncMtx.Unlock() 85 | return 86 | } 87 | 88 | ln, err := net.ListenUDP("udp", serverAddr) 89 | if err != nil { 90 | SLog.Error("Error opening UDP Server Socket: %s\n", err) 91 | f.syncMtx.Unlock() 92 | return 93 | } 94 | defer ln.Close() 95 | 96 | target, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", f.host, f.port)) 97 | 98 | if err != nil { 99 | SLog.Error("Error opening UDP Server Socket: %s\n", err) 100 | f.syncMtx.Unlock() 101 | return 102 | } 103 | 104 | SLog.Info("UDP Server Started") 105 | f.conn = ln 106 | f.target = target 107 | f.syncMtx.Unlock() 108 | 109 | for f.isRunning() { 110 | time.Sleep(time.Millisecond * 100) 111 | } 112 | 113 | } 114 | 115 | // endregion 116 | -------------------------------------------------------------------------------- /Display/ConsoleWritter.go: -------------------------------------------------------------------------------- 1 | package Display 2 | 3 | type ConsoleWritter struct { 4 | writeFunc func(string) (int, error) 5 | } 6 | 7 | func (w *ConsoleWritter) Write(p []byte) (n int, err error) { 8 | stringData := string(p) 9 | return w.writeFunc(stringData) 10 | } 11 | 12 | func NewConsoleWritter(writeFunc func(string) (int, error)) *ConsoleWritter { 13 | return &ConsoleWritter{writeFunc: writeFunc} 14 | } 15 | -------------------------------------------------------------------------------- /Frontend/AirspyDevice/AirspyDevice.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AirspyDevice.h 3 | * 4 | * Created on: 24/12/2016 5 | * Author: Lucas Teske 6 | */ 7 | 8 | #ifndef SRC_AIRSPYDEVICE_H_ 9 | #define SRC_AIRSPYDEVICE_H_ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | extern "C" { 19 | #include 20 | } 21 | 22 | #include "../DeviceParameters.h" 23 | class AirspyDevice { 24 | private: 25 | static std::string libraryVersion; 26 | 27 | GoDeviceCallback *cb; 28 | uint8_t boardId; 29 | std::string firmwareVersion; 30 | std::string partNumber; 31 | std::string serialNumber; 32 | std::vector availableSampleRates; 33 | std::string name; 34 | airspy_device* device; 35 | 36 | uint32_t sampleRate; 37 | uint32_t centerFrequency; 38 | uint8_t lnaGain; 39 | uint8_t vgaGain; 40 | uint8_t mixerGain; 41 | 42 | int SamplesAvailableCallback(airspy_transfer *transfer); 43 | public: 44 | AirspyDevice(GoDeviceCallback *cb); 45 | virtual ~AirspyDevice(); 46 | 47 | static void Initialize(); 48 | static void DeInitialize(); 49 | 50 | uint32_t SetSampleRate(uint32_t sampleRate); 51 | uint32_t SetCenterFrequency(uint32_t centerFrequency); 52 | const std::vector& GetAvailableSampleRates(); 53 | void Start(); 54 | void Stop(); 55 | void SetAGC(bool agc); 56 | 57 | bool Init(); 58 | void Destroy(); 59 | 60 | void SetLNAGain(uint8_t value); 61 | void SetVGAGain(uint8_t value); 62 | void SetMixerGain(uint8_t value); 63 | void SetBiasT(uint8_t value); 64 | 65 | uint32_t GetCenterFrequency(); 66 | 67 | const std::string &GetName(); 68 | 69 | uint32_t GetSampleRate(); 70 | 71 | void SetSamplesAvailableCallback(GoDeviceCallback *cb); 72 | 73 | }; 74 | 75 | #endif /* SRC_AIRSPYDEVICE_H_ */ 76 | -------------------------------------------------------------------------------- /Frontend/AirspyDevice/AirspyDevice.i: -------------------------------------------------------------------------------- 1 | %module(directors="1") AirspyDevice 2 | %{ 3 | #include "AirspyDevice.h" 4 | %} 5 | 6 | %insert(cgo_comment_typedefs) %{ 7 | #cgo CXXFLAGS: -std=c++11 -O3 8 | #cgo LDFLAGS: -l:libairspy.a -lusb-1.0 9 | %} 10 | 11 | %include "stdint.i" 12 | %include "stl.i" 13 | %include "std_vector.i" 14 | 15 | %feature("director") GoDeviceCallback; 16 | %rename("AirspyDeviceCallback") GoDeviceCallback; 17 | 18 | %include "../DeviceParameters.h" 19 | 20 | %template(Vector32u) std::vector; 21 | %template(Vector32f) std::vector; 22 | %template(Vector16i) std::vector; 23 | %template(Vector8i) std::vector; 24 | 25 | %include "./AirspyDevice.h" -------------------------------------------------------------------------------- /Frontend/AirspyDevice/AirspyDevice_wrap.h: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------------- 2 | * This file was automatically generated by SWIG (http://www.swig.org). 3 | * Version 3.0.12 4 | * 5 | * This file is not intended to be easily readable and contains a number of 6 | * coding conventions designed to improve portability and efficiency. Do not make 7 | * changes to this file unless you know what you are doing--modify the SWIG 8 | * interface file instead. 9 | * ----------------------------------------------------------------------------- */ 10 | 11 | // source: Frontend/AirspyDevice/AirspyDevice.i 12 | 13 | #ifndef SWIG_AirspyDevice_WRAP_H_ 14 | #define SWIG_AirspyDevice_WRAP_H_ 15 | 16 | class Swig_memory; 17 | 18 | class SwigDirector_AirspyDeviceCallback : public GoDeviceCallback 19 | { 20 | public: 21 | SwigDirector_AirspyDeviceCallback(int swig_p); 22 | void _swig_upcall_cbFloatIQ(void *data, int length) { 23 | GoDeviceCallback::cbFloatIQ(data,length); 24 | } 25 | virtual void cbFloatIQ(void *data, int length); 26 | void _swig_upcall_cbS16IQ(void *data, int length) { 27 | GoDeviceCallback::cbS16IQ(data,length); 28 | } 29 | virtual void cbS16IQ(void *data, int length); 30 | void _swig_upcall_cbS8IQ(void *data, int length) { 31 | GoDeviceCallback::cbS8IQ(data,length); 32 | } 33 | virtual void cbS8IQ(void *data, int length); 34 | void _swig_upcall_Info(std::string arg0) { 35 | GoDeviceCallback::Info(arg0); 36 | } 37 | virtual void Info(std::string arg0); 38 | void _swig_upcall_Error(std::string arg0) { 39 | GoDeviceCallback::Error(arg0); 40 | } 41 | virtual void Error(std::string arg0); 42 | void _swig_upcall_Warn(std::string arg0) { 43 | GoDeviceCallback::Warn(arg0); 44 | } 45 | virtual void Warn(std::string arg0); 46 | void _swig_upcall_Debug(std::string arg0) { 47 | GoDeviceCallback::Debug(arg0); 48 | } 49 | virtual void Debug(std::string arg0); 50 | virtual ~SwigDirector_AirspyDeviceCallback(); 51 | private: 52 | intgo go_val; 53 | Swig_memory *swig_mem; 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /Frontend/AirspyFrontend.go: -------------------------------------------------------------------------------- 1 | package Frontend 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/Frontend/AirspyDevice" 5 | ) 6 | 7 | // region Struct Definition 8 | type AirspyFrontend struct { 9 | device AirspyDevice.AirspyDevice 10 | goCb GoCallback 11 | goDirCb AirspyDevice.AirspyDeviceCallback 12 | } 13 | 14 | func MakeAirspyGoCallbackDirector(callback *GoCallback) AirspyDevice.AirspyDeviceCallback { 15 | return AirspyDevice.NewDirectorAirspyDeviceCallback(callback) 16 | } 17 | 18 | // endregion 19 | // region Constructor 20 | func NewAirspyFrontend() *AirspyFrontend { 21 | goCb := NewGoCallback() 22 | dirCb := MakeAirspyGoCallbackDirector(&goCb) 23 | afrnt := AirspyFrontend{ 24 | device: AirspyDevice.NewAirspyDevice(dirCb), 25 | goCb: goCb, 26 | } 27 | 28 | return &afrnt 29 | } 30 | func AirspyInitialize() { 31 | AirspyDevice.AirspyDeviceInitialize() 32 | } 33 | func AirspyDeinitialize() { 34 | AirspyDevice.AirspyDeviceDeInitialize() 35 | } 36 | 37 | // endregion 38 | // region Getters 39 | func (f *AirspyFrontend) GetName() string { 40 | return f.device.GetName() 41 | } 42 | func (f *AirspyFrontend) GetShortName() string { 43 | return f.device.GetName() 44 | } 45 | func (f *AirspyFrontend) GetAvailableSampleRates() []uint32 { 46 | var sampleRates = f.device.GetAvailableSampleRates() 47 | var sr = make([]uint32, sampleRates.Size()) 48 | for i := 0; i < int(sampleRates.Size()); i++ { 49 | sr[i] = uint32(sampleRates.Get(i)) 50 | } 51 | 52 | return sr 53 | } 54 | func (f *AirspyFrontend) GetCenterFrequency() uint32 { 55 | return uint32(f.device.GetCenterFrequency()) 56 | } 57 | func (f *AirspyFrontend) GetSampleRate() uint32 { 58 | return uint32(f.device.GetSampleRate()) 59 | } 60 | 61 | // endregion 62 | // region Setters 63 | func (f *AirspyFrontend) SetSamplesAvailableCallback(cb SamplesCallback) { 64 | f.goCb.callback = cb 65 | f.goDirCb = MakeAirspyGoCallbackDirector(&f.goCb) 66 | f.device.SetSamplesAvailableCallback(f.goDirCb) 67 | } 68 | func (f *AirspyFrontend) SetSampleRate(sampleRate uint32) uint32 { 69 | return uint32(f.device.SetSampleRate(uint(sampleRate))) 70 | } 71 | func (f *AirspyFrontend) SetCenterFrequency(centerFrequency uint32) uint32 { 72 | return uint32(f.device.SetCenterFrequency(uint(centerFrequency))) 73 | } 74 | 75 | // endregion 76 | // region Commands 77 | func (f *AirspyFrontend) Start() { 78 | f.device.Start() 79 | } 80 | func (f *AirspyFrontend) Stop() { 81 | f.device.Stop() 82 | } 83 | func (f *AirspyFrontend) SetAGC(agc bool) { 84 | f.device.SetAGC(agc) 85 | } 86 | func (f *AirspyFrontend) SetGain1(gain uint8) { 87 | f.device.SetLNAGain(gain) 88 | } 89 | func (f *AirspyFrontend) SetGain2(gain uint8) { 90 | f.device.SetVGAGain(gain) 91 | } 92 | func (f *AirspyFrontend) SetGain3(gain uint8) { 93 | f.device.SetMixerGain(gain) 94 | } 95 | func (f *AirspyFrontend) SetBiasT(biast bool) { 96 | val := uint8(0) 97 | if biast { 98 | val = 1 99 | } 100 | f.device.SetBiasT(val) 101 | } 102 | func (f *AirspyFrontend) Init() bool { 103 | return f.device.Init() 104 | } 105 | func (f *AirspyFrontend) Destroy() { 106 | f.device.Destroy() 107 | } 108 | 109 | func (f *AirspyFrontend) SetAntenna(string) {} 110 | 111 | // endregion 112 | -------------------------------------------------------------------------------- /Frontend/BaseFrontend.go: -------------------------------------------------------------------------------- 1 | package Frontend 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/Logger" 5 | "unsafe" 6 | ) 7 | 8 | const SampleTypeFloatIQ = 0 9 | const SampleTypeS16IQ = 1 10 | const SampleTypeS8IQ = 2 11 | 12 | type SampleCallbackData struct { 13 | ComplexArray []complex64 14 | Int16Array []int16 15 | Int8Array []int8 16 | SampleType int 17 | NumSamples int 18 | } 19 | 20 | type SamplesCallback func(data SampleCallbackData) 21 | 22 | type GoCallback struct { 23 | callback SamplesCallback 24 | } 25 | 26 | func NewGoCallback() GoCallback { 27 | return GoCallback{} 28 | } 29 | 30 | func (p *GoCallback) Info(str string) { 31 | SLog.Info("%s", str) 32 | } 33 | 34 | func (p *GoCallback) Error(str string) { 35 | SLog.Error("%s", str) 36 | } 37 | 38 | func (p *GoCallback) Warn(str string) { 39 | SLog.Warn("%s", str) 40 | } 41 | 42 | func (p *GoCallback) Debug(str string) { 43 | SLog.Debug("%s", str) 44 | } 45 | 46 | func (p *GoCallback) CbFloatIQ(data uintptr, length int) { 47 | const arrayLen = 1 << 20 48 | arr := (*[arrayLen]complex64)(unsafe.Pointer(data))[:length:length] 49 | if p.callback != nil { 50 | p.callback(SampleCallbackData{ 51 | ComplexArray: arr, 52 | NumSamples: length, 53 | SampleType: SampleTypeFloatIQ, 54 | }) 55 | } 56 | } 57 | 58 | func (p *GoCallback) CbS16IQ(data uintptr, length int) { 59 | // Length times two, because each sample contains an I and a Q in S16 60 | const arrayLen = 1 << 20 61 | var pairLength = length * 2 62 | arr := (*[arrayLen]int16)(unsafe.Pointer(data))[:pairLength:pairLength] 63 | if p.callback != nil { 64 | p.callback(SampleCallbackData{ 65 | Int16Array: arr, 66 | NumSamples: length, 67 | SampleType: SampleTypeS16IQ, 68 | }) 69 | } 70 | } 71 | 72 | func (p *GoCallback) CbS8IQ(data uintptr, length int) { 73 | // Length times two, because each sample contains an I and a Q in S8 74 | const arrayLen = 1 << 20 75 | var pairLength = length * 2 76 | arr := (*[arrayLen]int8)(unsafe.Pointer(data))[:pairLength:pairLength] 77 | if p.callback != nil { 78 | p.callback(SampleCallbackData{ 79 | Int8Array: arr, 80 | NumSamples: length, 81 | SampleType: SampleTypeS8IQ, 82 | }) 83 | } 84 | } 85 | 86 | type BaseFrontend interface { 87 | SetSampleRate(sampleRate uint32) uint32 88 | SetCenterFrequency(centerFrequency uint32) uint32 89 | GetAvailableSampleRates() []uint32 90 | Start() 91 | Stop() 92 | SetAntenna(value string) 93 | SetAGC(agc bool) 94 | SetGain1(value uint8) 95 | SetGain2(value uint8) 96 | SetGain3(value uint8) 97 | SetBiasT(value bool) 98 | GetCenterFrequency() uint32 99 | GetName() string 100 | GetShortName() string 101 | GetSampleRate() uint32 102 | SetSamplesAvailableCallback(cb SamplesCallback) 103 | Init() bool 104 | Destroy() 105 | } 106 | -------------------------------------------------------------------------------- /Frontend/CFileFrontend.go: -------------------------------------------------------------------------------- 1 | package Frontend 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | . "github.com/logrusorgru/aurora" 7 | "github.com/opensatelliteproject/SatHelperApp/Logger" 8 | "github.com/racerxdl/fastconvert" 9 | "os" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | const CFileFrontendBufferSize = 65535 15 | 16 | // region Struct Definition 17 | type CFileFrontend struct { 18 | sync.Mutex 19 | running bool 20 | filename string 21 | cb SamplesCallback 22 | sampleRate uint32 23 | centerFrequency uint32 24 | sampleBuffer []complex64 25 | fileHandler *os.File 26 | t0 time.Time 27 | fastAsPossible bool 28 | } 29 | 30 | // endregion 31 | // region Constructor 32 | func NewCFileFrontend(filename string) *CFileFrontend { 33 | return &CFileFrontend{ 34 | filename: filename, 35 | running: false, 36 | sampleRate: 0, 37 | centerFrequency: 0, 38 | cb: nil, 39 | fastAsPossible: false, 40 | } 41 | } 42 | 43 | // endregion 44 | // region Getters 45 | func (f *CFileFrontend) GetName() string { 46 | return fmt.Sprintf("CFileFrontend (%s)", f.filename) 47 | } 48 | func (f *CFileFrontend) GetShortName() string { 49 | return "CFileFrontend" 50 | } 51 | func (f *CFileFrontend) GetAvailableSampleRates() []uint32 { 52 | return make([]uint32, 0) 53 | } 54 | func (f *CFileFrontend) GetCenterFrequency() uint32 { 55 | return f.centerFrequency 56 | } 57 | func (f *CFileFrontend) GetSampleRate() uint32 { 58 | return f.sampleRate 59 | } 60 | func (f *CFileFrontend) EnableFastAsPossible() { 61 | f.fastAsPossible = true 62 | } 63 | 64 | // endregion 65 | // region Setters 66 | func (f *CFileFrontend) SetSamplesAvailableCallback(cb SamplesCallback) { 67 | f.cb = cb 68 | } 69 | func (f *CFileFrontend) SetSampleRate(sampleRate uint32) uint32 { 70 | f.sampleRate = sampleRate 71 | return sampleRate 72 | } 73 | func (f *CFileFrontend) SetCenterFrequency(centerFrequency uint32) uint32 { 74 | f.centerFrequency = centerFrequency 75 | return centerFrequency 76 | } 77 | 78 | // endregion 79 | // region Commands 80 | func (f *CFileFrontend) Init() bool { 81 | return true 82 | } 83 | func (f *CFileFrontend) Destroy() {} 84 | func (f *CFileFrontend) isRunning() bool { 85 | f.Lock() 86 | defer f.Unlock() 87 | return f.running 88 | } 89 | func (f *CFileFrontend) Start() { 90 | f.Lock() 91 | defer f.Unlock() 92 | 93 | if f.running { 94 | SLog.Error("CFileFrontend is already running.") 95 | return 96 | } 97 | 98 | f.running = true 99 | 100 | go func(frontend *CFileFrontend) { 101 | SLog.Info("CFileFrontend Routine started") 102 | f, err := os.Open(f.filename) 103 | 104 | var period = CFileFrontendBufferSize / float32(frontend.sampleRate) 105 | 106 | if err != nil { 107 | SLog.Error("Error opening file %s: %s", Bold(frontend.filename), Bold(err)) 108 | frontend.running = false 109 | return 110 | } 111 | defer frontend.fileHandler.Close() 112 | 113 | frontend.fileHandler = f 114 | frontend.t0 = time.Now() 115 | frontend.sampleBuffer = make([]complex64, CFileFrontendBufferSize) 116 | 117 | var reader = bufio.NewReader(f) 118 | 119 | if frontend.fastAsPossible { 120 | period /= 8 // Avoid lock up 121 | } 122 | 123 | buff := make([]byte, len(frontend.sampleBuffer)*4*2) 124 | 125 | for frontend.isRunning() { 126 | if float32(time.Since(frontend.t0).Seconds()) >= period { 127 | _, err = reader.Read(buff) 128 | if err != nil { 129 | SLog.Error("Error reading input CFile: %s", Bold(err)) 130 | frontend.running = false 131 | break 132 | } 133 | fastconvert.ReadByteArrayToComplex64Array(buff, frontend.sampleBuffer) 134 | if frontend.cb != nil { 135 | var cbData = SampleCallbackData{ 136 | SampleType: SampleTypeFloatIQ, 137 | NumSamples: len(frontend.sampleBuffer), 138 | ComplexArray: frontend.sampleBuffer, 139 | } 140 | frontend.cb(cbData) 141 | } 142 | frontend.t0 = time.Now() 143 | } 144 | if !frontend.fastAsPossible { 145 | time.Sleep(time.Duration((period / 100) * float32(time.Second))) 146 | } 147 | } 148 | SLog.Error("CFileFrontend Routine ended") 149 | }(f) 150 | } 151 | 152 | func (f *CFileFrontend) Stop() { 153 | f.Lock() 154 | defer f.Unlock() 155 | 156 | if !f.running { 157 | SLog.Error("CFileFrontend is not running") 158 | return 159 | } 160 | 161 | f.running = false 162 | } 163 | 164 | func (f *CFileFrontend) SetAntenna(string) {} 165 | func (f *CFileFrontend) SetAGC(bool) {} 166 | func (f *CFileFrontend) SetGain1(uint8) {} 167 | func (f *CFileFrontend) SetGain2(uint8) {} 168 | func (f *CFileFrontend) SetGain3(uint8) {} 169 | func (f *CFileFrontend) SetBiasT(bool) {} 170 | 171 | // endregion 172 | -------------------------------------------------------------------------------- /Frontend/DeviceParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * DeviceParameters.h 3 | * 4 | * Created on: 31/01/2017 5 | * Author: Lucas Teske 6 | */ 7 | 8 | #ifndef SRC_DEVICEPARAMETERS_H_ 9 | #define SRC_DEVICEPARAMETERS_H_ 10 | 11 | 12 | #define FRONTEND_SAMPLETYPE_FLOATIQ 0 13 | #define FRONTEND_SAMPLETYPE_S16IQ 1 14 | #define FRONTEND_SAMPLETYPE_S8IQ 2 15 | 16 | enum TLogLevel {logERROR, logWARN, logINFO, logDEBUG}; 17 | 18 | class GoDeviceCallback { 19 | public: 20 | virtual void cbFloatIQ(void *data, int length) {} 21 | virtual void cbS16IQ(void *data, int length) {} 22 | virtual void cbS8IQ(void *data, int length) {} 23 | virtual void Info(std::string) {} 24 | virtual void Error(std::string) {} 25 | virtual void Warn(std::string) {} 26 | virtual void Debug(std::string) {} 27 | virtual ~GoDeviceCallback() {} 28 | }; 29 | 30 | 31 | class Log { 32 | private: 33 | GoDeviceCallback *cb; 34 | TLogLevel level; 35 | protected: 36 | std::ostringstream os; 37 | public: 38 | Log(GoDeviceCallback *cb) : cb(cb) {} 39 | std::ostringstream& Get(TLogLevel level = logINFO) { 40 | this->level = level; 41 | return os; 42 | } 43 | 44 | ~Log() { 45 | if (cb != NULL) { 46 | switch (this->level) { 47 | case logERROR: cb->Error(os.str()); break; 48 | case logDEBUG: cb->Debug(os.str()); break; 49 | case logWARN: cb->Warn(os.str()); break; 50 | default: cb->Info(os.str()); break; 51 | } 52 | } 53 | } 54 | }; 55 | 56 | #endif /* SRC_DEVICEPARAMETERS_H_ */ -------------------------------------------------------------------------------- /Frontend/RTLSDRDevice/RTLSDRDevice.i: -------------------------------------------------------------------------------- 1 | %module(directors="1") RTLSDRDevice 2 | %{ 3 | #include "RtlFrontend.h" 4 | %} 5 | 6 | %insert(cgo_comment_typedefs) %{ 7 | #cgo CXXFLAGS: -std=c++11 -O3 8 | #cgo LDFLAGS: -l:librtlsdr.a -lusb-1.0 9 | %} 10 | 11 | %include "stdint.i" 12 | %include "stl.i" 13 | %include "std_vector.i" 14 | 15 | %feature("director") GoDeviceCallback; 16 | %rename("RTLSDRDeviceCallback") GoDeviceCallback; 17 | 18 | %include "../DeviceParameters.h" 19 | 20 | %template(Vector32u) std::vector; 21 | %template(Vector32f) std::vector; 22 | %template(Vector16i) std::vector; 23 | %template(Vector8i) std::vector; 24 | 25 | %include "./RtlFrontend.h" -------------------------------------------------------------------------------- /Frontend/RTLSDRDevice/RTLSDRDevice_wrap.h: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------------- 2 | * This file was automatically generated by SWIG (http://www.swig.org). 3 | * Version 3.0.12 4 | * 5 | * This file is not intended to be easily readable and contains a number of 6 | * coding conventions designed to improve portability and efficiency. Do not make 7 | * changes to this file unless you know what you are doing--modify the SWIG 8 | * interface file instead. 9 | * ----------------------------------------------------------------------------- */ 10 | 11 | // source: Frontend/RTLSDRDevice/RTLSDRDevice.i 12 | 13 | #ifndef SWIG_RTLSDRDevice_WRAP_H_ 14 | #define SWIG_RTLSDRDevice_WRAP_H_ 15 | 16 | class Swig_memory; 17 | 18 | class SwigDirector_RTLSDRDeviceCallback : public GoDeviceCallback 19 | { 20 | public: 21 | SwigDirector_RTLSDRDeviceCallback(int swig_p); 22 | void _swig_upcall_cbFloatIQ(void *data, int length) { 23 | GoDeviceCallback::cbFloatIQ(data,length); 24 | } 25 | virtual void cbFloatIQ(void *data, int length); 26 | void _swig_upcall_cbS16IQ(void *data, int length) { 27 | GoDeviceCallback::cbS16IQ(data,length); 28 | } 29 | virtual void cbS16IQ(void *data, int length); 30 | void _swig_upcall_cbS8IQ(void *data, int length) { 31 | GoDeviceCallback::cbS8IQ(data,length); 32 | } 33 | virtual void cbS8IQ(void *data, int length); 34 | void _swig_upcall_Info(std::string arg0) { 35 | GoDeviceCallback::Info(arg0); 36 | } 37 | virtual void Info(std::string arg0); 38 | void _swig_upcall_Error(std::string arg0) { 39 | GoDeviceCallback::Error(arg0); 40 | } 41 | virtual void Error(std::string arg0); 42 | void _swig_upcall_Warn(std::string arg0) { 43 | GoDeviceCallback::Warn(arg0); 44 | } 45 | virtual void Warn(std::string arg0); 46 | void _swig_upcall_Debug(std::string arg0) { 47 | GoDeviceCallback::Debug(arg0); 48 | } 49 | virtual void Debug(std::string arg0); 50 | virtual ~SwigDirector_RTLSDRDeviceCallback(); 51 | private: 52 | intgo go_val; 53 | Swig_memory *swig_mem; 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /Frontend/RTLSDRDevice/RtlFrontend.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * RtlFrontend.cpp 3 | * 4 | * Created on: 25/02/2017 5 | * Author: Lucas Teske 6 | */ 7 | 8 | #include "RtlFrontend.h" 9 | #include 10 | 11 | std::vector RtlFrontend::supportedSampleRates = { 12 | 240000, 300000, 960000, 1152000, 1200000, 1440000, 1600000, 1800000, 1920000, 2400000, 2560000, 2880000, 3200000 13 | }; 14 | 15 | RtlFrontend::RtlFrontend(GoDeviceCallback *cb) : 16 | sampleRate(2560000), centerFrequency(106300000), deviceId(0), alpha(0), iavg(0), qavg(0), lnaGain(0), vgaGain(0), mixerGain(0), 17 | agc(false) { 18 | this->cb = cb; 19 | for (int i = 0; i < 256; i++) { 20 | lut[i] = (i - 128) * (1.f / 127.f); 21 | } 22 | mainThread = NULL; 23 | } 24 | 25 | RtlFrontend::~RtlFrontend() { 26 | rtlsdr_close(device); 27 | } 28 | 29 | bool RtlFrontend::Init() { 30 | if (rtlsdr_open(&device, deviceId) != 0) { 31 | Log(cb).Get(logERROR) << "Failed to open rtlsdr with id " << deviceId << std::endl; 32 | return false; 33 | } 34 | 35 | deviceName = std::string(rtlsdr_get_device_name(deviceId)); 36 | 37 | return true; 38 | } 39 | 40 | void RtlFrontend::SetBiasT(uint8_t value) { 41 | std::cerr << "BiasT in RtlSdr is not supported by OSP" << std::endl; 42 | } 43 | 44 | uint32_t RtlFrontend::SetSampleRate(uint32_t sampleRate) { 45 | this->sampleRate = sampleRate; 46 | rtlsdr_set_sample_rate(device, sampleRate); 47 | return sampleRate; 48 | } 49 | 50 | uint32_t RtlFrontend::SetCenterFrequency(uint32_t centerFrequency) { 51 | this->centerFrequency = centerFrequency; 52 | rtlsdr_set_center_freq(device, centerFrequency); 53 | return centerFrequency; 54 | } 55 | 56 | const std::vector& RtlFrontend::GetAvailableSampleRates() { 57 | return RtlFrontend::supportedSampleRates; 58 | } 59 | 60 | void RtlFrontend::Start() { 61 | alpha = 1.f - exp(-1.0 / (sampleRate * 0.05f)); 62 | iavg = 0; 63 | qavg = 0; 64 | 65 | if (mainThread != NULL) { 66 | throw SatHelperException("The worker is already running!"); 67 | } 68 | 69 | if (rtlsdr_set_sample_rate(device, sampleRate) != 0) { 70 | std::cerr << "Cannot set sample rate to " << sampleRate << std::endl; 71 | throw SatHelperException("Cannot set sample rate."); 72 | } 73 | 74 | if (rtlsdr_set_center_freq(device, centerFrequency) != 0) { 75 | std::cerr << "Cannot set center frequency to " << centerFrequency 76 | << std::endl; 77 | throw SatHelperException("Cannot set center frequency."); 78 | } 79 | 80 | if (rtlsdr_set_tuner_gain_mode(device, !agc) != 0) { 81 | std::cerr << "Cannot enable / disable Tuner AGC" << std::endl; 82 | throw SatHelperException("Cannot set Tuner AGC"); 83 | } 84 | 85 | if (rtlsdr_set_tuner_gain_ext(device, lnaGain, mixerGain, vgaGain) != 0) { 86 | std::cerr << "Cannot set Tuner Gains" << std::endl; 87 | throw SatHelperException("Cannot set Tuner Gains"); 88 | } 89 | 90 | if (rtlsdr_reset_buffer(device) != 0) { 91 | throw SatHelperException("Cannot reset device buffer"); 92 | } 93 | 94 | mainThread = new std::thread(&RtlFrontend::threadWork, this); 95 | } 96 | 97 | void RtlFrontend::rtlCallback(unsigned char *data, unsigned int length, void *ctx) { 98 | RtlFrontend *frontend = (RtlFrontend *)ctx; 99 | frontend->internalCallback(data, length); 100 | } 101 | 102 | void RtlFrontend::threadWork() { 103 | rtlsdr_read_async(device, rtlCallback, this, 0, 16384); 104 | } 105 | 106 | void RtlFrontend::internalCallback(unsigned char *data, unsigned int length) { 107 | float *iq = new float[length]; 108 | 109 | for (unsigned int i=0; icb->cbFloatIQ(iq, length/2); 122 | } 123 | 124 | delete[] iq; 125 | } 126 | 127 | void RtlFrontend::refreshGains() { 128 | rtlsdr_set_tuner_gain_ext(device, lnaGain, mixerGain, vgaGain); 129 | } 130 | 131 | void RtlFrontend::Stop() { 132 | rtlsdr_cancel_async(device); 133 | if (mainThread != NULL && mainThread->joinable()) { 134 | mainThread->join(); 135 | } 136 | mainThread = NULL; 137 | } 138 | 139 | void RtlFrontend::SetAGC(bool agc) { 140 | rtlsdr_set_tuner_gain_mode(device, !agc); 141 | this->agc = agc; 142 | } 143 | 144 | void RtlFrontend::SetLNAGain(uint8_t value) { 145 | lnaGain = value; 146 | refreshGains(); 147 | } 148 | 149 | void RtlFrontend::SetVGAGain(uint8_t value) { 150 | vgaGain = value; 151 | refreshGains(); 152 | } 153 | 154 | void RtlFrontend::SetMixerGain(uint8_t value) { 155 | mixerGain = value; 156 | refreshGains(); 157 | } 158 | 159 | uint32_t RtlFrontend::GetCenterFrequency() { 160 | return centerFrequency; 161 | } 162 | 163 | const std::string &RtlFrontend::GetName() { 164 | return deviceName; 165 | } 166 | 167 | uint32_t RtlFrontend::GetSampleRate() { 168 | return sampleRate; 169 | } 170 | 171 | void RtlFrontend::SetSamplesAvailableCallback(GoDeviceCallback *cb) { 172 | this->cb = cb; 173 | } 174 | -------------------------------------------------------------------------------- /Frontend/RTLSDRDevice/RtlFrontend.h: -------------------------------------------------------------------------------- 1 | /* 2 | * RtlFrontend.h 3 | * 4 | * Created on: 25/02/2017 5 | * Author: Lucas Teske 6 | */ 7 | 8 | #ifndef SRC_RTLFRONTEND_H_ 9 | #define SRC_RTLFRONTEND_H_ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | extern "C" { 21 | #include 22 | } 23 | 24 | #include "../DeviceParameters.h" 25 | 26 | class RtlFrontend { 27 | private: 28 | static std::vector supportedSampleRates; 29 | 30 | uint32_t sampleRate; 31 | uint32_t centerFrequency; 32 | int deviceId; 33 | rtlsdr_dev_t *device; 34 | std::string deviceName; 35 | float lut[256]; 36 | float alpha; 37 | float iavg; 38 | float qavg; 39 | std::thread *mainThread; 40 | uint8_t lnaGain; 41 | uint8_t vgaGain; 42 | uint8_t mixerGain; 43 | bool agc; 44 | GoDeviceCallback *cb; 45 | 46 | void threadWork(); 47 | void refreshGains(); 48 | void internalCallback(unsigned char *data, unsigned int length); 49 | protected: 50 | static void rtlCallback(unsigned char *data, unsigned int length, void *ctx); 51 | 52 | public: 53 | RtlFrontend(GoDeviceCallback *cb); 54 | virtual ~RtlFrontend(); 55 | 56 | uint32_t SetSampleRate(uint32_t sampleRate); 57 | uint32_t SetCenterFrequency(uint32_t centerFrequency); 58 | const std::vector& GetAvailableSampleRates(); 59 | void Start(); 60 | void Stop(); 61 | void SetAGC(bool agc); 62 | 63 | void SetLNAGain(uint8_t value); 64 | void SetVGAGain(uint8_t value); 65 | void SetMixerGain(uint8_t value); 66 | void SetBiasT(uint8_t value); 67 | uint32_t GetCenterFrequency(); 68 | 69 | const std::string &GetName(); 70 | 71 | uint32_t GetSampleRate(); 72 | 73 | void SetSamplesAvailableCallback(GoDeviceCallback *cb); 74 | bool Init(); 75 | }; 76 | 77 | #endif /* SRC_RTLFRONTEND_H_ */ 78 | -------------------------------------------------------------------------------- /Frontend/RTLSDRFrontend.go: -------------------------------------------------------------------------------- 1 | package Frontend 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/Frontend/RTLSDRDevice" 5 | ) 6 | 7 | // region Struct Definition 8 | type RTLSDRFrontend struct { 9 | device RTLSDRDevice.RtlFrontend 10 | goCb GoCallback 11 | goDirCb RTLSDRDevice.RTLSDRDeviceCallback 12 | } 13 | 14 | func MakeRTLSDRGoCallbackDirector(callback *GoCallback) RTLSDRDevice.RTLSDRDeviceCallback { 15 | return RTLSDRDevice.NewDirectorRTLSDRDeviceCallback(callback) 16 | } 17 | 18 | // endregion 19 | // region Constructor 20 | func NewRTLSDRFrontend() *RTLSDRFrontend { 21 | goCb := NewGoCallback() 22 | dirCb := MakeRTLSDRGoCallbackDirector(&goCb) 23 | afrnt := RTLSDRFrontend{ 24 | device: RTLSDRDevice.NewRtlFrontend(dirCb), 25 | goCb: goCb, 26 | } 27 | 28 | return &afrnt 29 | } 30 | 31 | // endregion 32 | // region Getters 33 | func (f *RTLSDRFrontend) GetName() string { 34 | return f.device.GetName() 35 | } 36 | 37 | func (f *RTLSDRFrontend) GetShortName() string { 38 | return f.device.GetName() 39 | } 40 | 41 | func (f *RTLSDRFrontend) GetAvailableSampleRates() []uint32 { 42 | var sampleRates = f.device.GetAvailableSampleRates() 43 | var sr = make([]uint32, sampleRates.Size()) 44 | for i := 0; i < int(sampleRates.Size()); i++ { 45 | sr[i] = uint32(sampleRates.Get(i)) 46 | } 47 | 48 | return sr 49 | } 50 | 51 | func (f *RTLSDRFrontend) GetCenterFrequency() uint32 { 52 | return uint32(f.device.GetCenterFrequency()) 53 | } 54 | 55 | func (f *RTLSDRFrontend) GetSampleRate() uint32 { 56 | return uint32(f.device.GetSampleRate()) 57 | } 58 | 59 | // endregion 60 | // region Setters 61 | func (f *RTLSDRFrontend) SetSamplesAvailableCallback(cb SamplesCallback) { 62 | f.goCb.callback = cb 63 | f.goDirCb = MakeRTLSDRGoCallbackDirector(&f.goCb) 64 | f.device.SetSamplesAvailableCallback(f.goDirCb) 65 | } 66 | 67 | func (f *RTLSDRFrontend) SetSampleRate(sampleRate uint32) uint32 { 68 | return uint32(f.device.SetSampleRate(uint(sampleRate))) 69 | } 70 | 71 | func (f *RTLSDRFrontend) SetCenterFrequency(centerFrequency uint32) uint32 { 72 | return uint32(f.device.SetCenterFrequency(uint(centerFrequency))) 73 | } 74 | 75 | // endregion 76 | // region Commands 77 | func (f *RTLSDRFrontend) Start() { 78 | f.device.Start() 79 | } 80 | 81 | func (f *RTLSDRFrontend) Stop() { 82 | f.device.Stop() 83 | } 84 | 85 | func (f *RTLSDRFrontend) SetAGC(agc bool) { 86 | f.device.SetAGC(agc) 87 | } 88 | 89 | func (f *RTLSDRFrontend) SetGain1(gain uint8) { 90 | f.device.SetLNAGain(gain) 91 | } 92 | 93 | func (f *RTLSDRFrontend) SetGain2(gain uint8) { 94 | f.device.SetVGAGain(gain) 95 | } 96 | 97 | func (f *RTLSDRFrontend) SetGain3(gain uint8) { 98 | f.device.SetMixerGain(gain) 99 | } 100 | 101 | func (f *RTLSDRFrontend) SetBiasT(biast bool) { 102 | val := uint8(0) 103 | if biast { 104 | val = 1 105 | } 106 | f.device.SetBiasT(val) 107 | } 108 | 109 | func (f *RTLSDRFrontend) Init() bool { 110 | return f.device.Init() 111 | } 112 | 113 | func (f *RTLSDRFrontend) Destroy() {} 114 | 115 | func (f *RTLSDRFrontend) SetAntenna(string) {} 116 | 117 | // endregion 118 | -------------------------------------------------------------------------------- /Frontend/SpyserverFrontend.go: -------------------------------------------------------------------------------- 1 | package Frontend 2 | 3 | import ( 4 | "fmt" 5 | "github.com/opensatelliteproject/SatHelperApp" 6 | "github.com/opensatelliteproject/SatHelperApp/Logger" 7 | "github.com/racerxdl/spy2go/spyserver" 8 | "github.com/racerxdl/spy2go/spytypes" 9 | ) 10 | 11 | // region Struct Definition 12 | type SpyserverFrontend struct { 13 | ss *spyserver.Spyserver 14 | cb SamplesCallback 15 | } 16 | 17 | // endregion 18 | // region Constructor 19 | func NewSpyserverFrontend(hostname string, port int) *SpyserverFrontend { 20 | spyserver.SoftwareID = fmt.Sprintf("SatHelperApp %s.%s", SatHelperApp.GetVersion(), SatHelperApp.GetRevision()) 21 | ss := spyserver.MakeSpyserver(hostname, port) 22 | afrnt := SpyserverFrontend{ 23 | ss: ss, 24 | } 25 | ss.SetCallback(&afrnt) 26 | 27 | return &afrnt 28 | } 29 | 30 | func (f *SpyserverFrontend) OnData(dType int, data interface{}) { 31 | cbData := SampleCallbackData{} 32 | 33 | if dType == spytypes.SamplesComplex64 { 34 | cbData.SampleType = SampleTypeFloatIQ 35 | cbData.ComplexArray = data.([]complex64) 36 | cbData.NumSamples = len(cbData.ComplexArray) 37 | } else if dType == spytypes.SamplesComplex32 { 38 | samples := data.([]spytypes.ComplexInt16) 39 | cbData.SampleType = SampleTypeS16IQ 40 | cbData.Int16Array = make([]int16, len(samples)*2) 41 | cbData.NumSamples = len(samples) 42 | for i := 0; i < len(samples); i++ { 43 | cbData.Int16Array[i*2] = samples[i].Real 44 | cbData.Int16Array[i*2+1] = samples[i].Imag 45 | } 46 | } else if dType == spytypes.SamplesComplexUInt8 { 47 | cbData.SampleType = SampleTypeS8IQ 48 | samples := data.([]spytypes.ComplexUInt8) 49 | cbData.Int8Array = make([]int8, len(samples)*2) 50 | for i := 0; i < len(samples); i++ { 51 | cbData.Int8Array[i*2] = int8(samples[i].Real) 52 | cbData.Int8Array[i*2+1] = int8(samples[i].Imag) 53 | } 54 | cbData.NumSamples = len(samples) 55 | } else if dType == spytypes.DeviceSync { 56 | SLog.Info("Got device sync!") 57 | return 58 | } 59 | 60 | if f.cb != nil { 61 | f.cb(cbData) 62 | } 63 | } 64 | 65 | // endregion 66 | // region Getters 67 | func (f *SpyserverFrontend) GetName() string { 68 | return f.ss.GetName() 69 | } 70 | func (f *SpyserverFrontend) GetShortName() string { 71 | return f.ss.GetName() 72 | } 73 | func (f *SpyserverFrontend) GetAvailableSampleRates() []uint32 { 74 | return f.ss.GetAvailableSampleRates() 75 | } 76 | func (f *SpyserverFrontend) GetCenterFrequency() uint32 { 77 | return f.ss.GetCenterFrequency() 78 | } 79 | func (f *SpyserverFrontend) GetSampleRate() uint32 { 80 | return f.ss.GetSampleRate() 81 | } 82 | 83 | // endregion 84 | // region Setters 85 | func (f *SpyserverFrontend) SetSamplesAvailableCallback(cb SamplesCallback) { 86 | f.cb = cb 87 | } 88 | func (f *SpyserverFrontend) SetSampleRate(sampleRate uint32) uint32 { 89 | return f.ss.SetSampleRate(sampleRate) 90 | } 91 | func (f *SpyserverFrontend) SetCenterFrequency(centerFrequency uint32) uint32 { 92 | return f.ss.SetCenterFrequency(centerFrequency) 93 | } 94 | 95 | // endregion 96 | // region Commands 97 | func (f *SpyserverFrontend) Start() { 98 | f.ss.Start() 99 | } 100 | func (f *SpyserverFrontend) Stop() { 101 | f.ss.Stop() 102 | } 103 | func (f *SpyserverFrontend) SetAGC(agc bool) { 104 | SLog.Warn("AGC not supported by SpyServer Frontend") 105 | } 106 | func (f *SpyserverFrontend) SetGain1(gain uint8) { 107 | f.ss.SetGain(uint32(gain)) 108 | } 109 | func (f *SpyserverFrontend) SetGain2(gain uint8) {} 110 | func (f *SpyserverFrontend) SetGain3(gain uint8) {} 111 | func (f *SpyserverFrontend) SetBiasT(biast bool) { 112 | SLog.Warn("BiasT not supported by SpyServer Frontend") 113 | } 114 | func (f *SpyserverFrontend) Init() bool { 115 | f.ss.Connect() 116 | return f.ss.IsConnected 117 | } 118 | 119 | func (f *SpyserverFrontend) Destroy() { 120 | f.ss.Disconnect() 121 | } 122 | 123 | func (f *SpyserverFrontend) SetAntenna(string) {} 124 | 125 | // endregion 126 | -------------------------------------------------------------------------------- /ImageProcessor/GOESABI.go: -------------------------------------------------------------------------------- 1 | package ImageProcessor 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor/ImageTools" 5 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor/Projector" 6 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor/Structs" 7 | "github.com/opensatelliteproject/SatHelperApp/Logger" 8 | "github.com/opensatelliteproject/SatHelperApp/Tools" 9 | "github.com/opensatelliteproject/SatHelperApp/XRIT" 10 | "github.com/opensatelliteproject/SatHelperApp/XRIT/Geo" 11 | "io/ioutil" 12 | "os" 13 | "path" 14 | "path/filepath" 15 | "regexp" 16 | "strings" 17 | ) 18 | 19 | var NOAANameRegex = regexp.MustCompile(`OR_ABI-(.*)-(.*)_(.*)_(s.*).*`) 20 | 21 | const visChan = "C02" 22 | const irChan = "C14" 23 | 24 | func ProcessGOESABI(ip *ImageProcessor, filename string, xh *XRIT.Header) { 25 | if xh.SubProduct().ID == 0 && !(xh.SegmentIdentificationHeader != nil && xh.SegmentIdentificationHeader.COMS1) { // Mesoscales and unknown data 26 | PlainLRITImage(ip, filename, xh) 27 | return 28 | } 29 | curveManipulator := ImageTools.GetVisibleCurveManipulator() 30 | 31 | basename := path.Base(filename) 32 | name := strings.TrimSuffix(basename, filepath.Ext(basename)) 33 | 34 | if xh.SegmentIdentificationHeader != nil && xh.SegmentIdentificationHeader.COMS1 { 35 | // Remove last 3 bytes from the name 36 | name = name[:len(name)-3] 37 | } 38 | 39 | if ip.MultiSegmentCache[name] == nil { 40 | ip.MultiSegmentCache[name] = Structs.MakeMultiSegmentImage(name, xh.SubProduct().ID, int(xh.SegmentIdentificationHeader.ImageID)) 41 | } 42 | 43 | ms := ip.MultiSegmentCache[name] 44 | ms.PutSegment(filename, xh) 45 | 46 | if ms.Done() { 47 | SLog.Info("Got all segments for %s", name) 48 | err, outname := ImageTools.DumpMultiSegment(ms, ip.GetMapDrawer(), curveManipulator, ip.reproject, ip.enhance, ip.metadata) 49 | if err != nil { 50 | SLog.Error("Error dumping Multi Segment Image %s: %s", name, err) 51 | } 52 | 53 | SLog.Info("New image %s", outname) 54 | 55 | if len(ip.cutRegions) > 0 { 56 | err = ImageTools.DumpCutRegion(ms, ip.GetMapDrawer(), ip.GetMapCutter(), curveManipulator, ip.reproject, ip.enhance, ip.metadata, ip.cutRegions) 57 | if err != nil { 58 | SLog.Error("Error processing region cuts: %s", err) 59 | } 60 | } 61 | 62 | delete(ip.MultiSegmentCache, name) 63 | 64 | if purgeFiles { 65 | if !ip.GetFalseColor() || !ms.FirstSegmentHeader.IsFalseColorPiece() { 66 | ms.Purge() 67 | } 68 | } 69 | 70 | if ms.FirstSegmentHeader.IsFalseColorPiece() { 71 | folder := path.Dir(ms.FirstSegmentFilename) 72 | nomapFile := path.Join(folder, ImageTools.GetNoMapName(ms.Name, "")) 73 | ProcessFalseColor(ip, ms.FirstSegmentHeader, nomapFile) 74 | } 75 | } 76 | } 77 | 78 | func ProcessFalseColor(ip *ImageProcessor, xh *XRIT.Header, filename string) { 79 | if !NOAANameRegex.MatchString(path.Base(filename)) { 80 | SLog.Debug("Filename %s does not match noaa name. Not continuing...", filename) 81 | return 82 | } 83 | 84 | // 0 => full string, 1 => Level-Product, 2 => Mode/Channel, 3 => Satellite Name, 4 => Group, 5 => File Stamp 85 | groups := NOAANameRegex.FindStringSubmatch(path.Base(filename)) 86 | 87 | mdch := groups[2] 88 | md := mdch[:2] 89 | name := groups[4] 90 | 91 | vismdch := md + visChan 92 | irmdch := md + irChan 93 | 94 | visFilename := strings.Replace(filename, mdch, vismdch, -1) 95 | irFilename := strings.Replace(filename, mdch, irmdch, -1) 96 | fsclrFileName := strings.Replace(filename, mdch, md+"C99", -1) 97 | fsclrFileName = strings.Replace(fsclrFileName, "-nomap", "", -1) 98 | 99 | if !Tools.Exists(visFilename) || !Tools.Exists(irFilename) { 100 | // Not Ready 101 | return 102 | } 103 | 104 | if Tools.Exists(fsclrFileName) { 105 | SLog.Debug("Skipping generating false color. File exists...") 106 | return 107 | } 108 | 109 | SLog.Info("Generating false color for %s", name) 110 | 111 | vis, err := ImageTools.LoadImageGrayScale(visFilename) 112 | if err != nil { 113 | SLog.Error("Error loading visible image at %s: %s", visFilename, err) 114 | return 115 | } 116 | 117 | ir, err := ImageTools.LoadImageGrayScale(irFilename) 118 | if err != nil { 119 | SLog.Error("Error loading infrared image at %s: %s", irFilename, err) 120 | return 121 | } 122 | 123 | falseLut := ImageTools.GetFalseColorLUT() 124 | 125 | enhancer := ImageTools.MakeImageEnhancerEmpty(false) 126 | 127 | fsclr, err := falseLut.Apply(vis, ir) 128 | if err != nil { 129 | SLog.Error("Error applying false color LUT: %s", err) 130 | return 131 | } 132 | 133 | gc, err := Geo.MakeGeoConverterFromXRIT(xh) 134 | 135 | if err == nil { 136 | if ip.GetReproject() { 137 | SLog.Debug("Reprojection Enabled, reprojecting FalseColor") 138 | proj := Projector.MakeProjector(gc) 139 | fsclr = proj.ReprojectLinearMultiThread(fsclr) 140 | gc = Projector.MakeLinearConverter(fsclr.Bounds().Dx(), fsclr.Bounds().Dy(), gc) 141 | } 142 | mapDrawer := ip.GetMapDrawer() 143 | 144 | if mapDrawer != nil { 145 | SLog.Debug("Map Drawer Enabled, drawing at FalseColor") 146 | mapDrawer.DrawMap(fsclr, gc) 147 | } 148 | } else { 149 | SLog.Error("Cannot crate GeoConverter: %s", err) 150 | } 151 | xh.NOAASpecificHeader.ProductSubID = 99 // False Color 152 | 153 | if ip.metadata { 154 | im2, err := enhancer.DrawMetaWithoutScale("", fsclr, xh) 155 | if err != nil { 156 | SLog.Error("Error drawing metadata: %s", err) 157 | } 158 | 159 | if im2 != nil { 160 | fsclr = im2 161 | } 162 | } 163 | 164 | metaName := strings.Replace(fsclrFileName, ".png", ".json", -1) 165 | err = ioutil.WriteFile(metaName, []byte(xh.ToJSON()), os.ModePerm) 166 | if err != nil { 167 | SLog.Error("Cannot write Meta file %s: %s", metaName, err) 168 | } 169 | 170 | err = ImageTools.SaveImage(fsclrFileName, fsclr) 171 | if err != nil { 172 | SLog.Error("Error saving false color image to %s: %s", fsclrFileName, err) 173 | return 174 | } 175 | 176 | SLog.Debug("Removing %s", visFilename) 177 | err = os.Remove(visFilename) 178 | if err != nil { 179 | SLog.Error("Error erasing %s: %s", visFilename, err) 180 | } 181 | 182 | SLog.Debug("Removing %s", irFilename) 183 | err = os.Remove(irFilename) 184 | if err != nil { 185 | SLog.Error("Error erasing %s: %s", irFilename, err) 186 | } 187 | 188 | SLog.Info("New image %s", fsclrFileName) 189 | } 190 | -------------------------------------------------------------------------------- /ImageProcessor/ImageData/README.md: -------------------------------------------------------------------------------- 1 | False Color LUT from https://github.com/hdoverobinson/wx-star_false-color 2 | 3 | Thanks hdoverobinson for the amazing work with the LUT! 4 | -------------------------------------------------------------------------------- /ImageProcessor/ImageData/etc.go: -------------------------------------------------------------------------------- 1 | package ImageData 2 | 3 | import ( 4 | "github.com/golang/freetype/truetype" 5 | "github.com/llgcode/draw2d" 6 | ) 7 | 8 | func init() { 9 | fontBytes := MustAsset("FreeMono.ttf") 10 | loadedFont, err := truetype.Parse(fontBytes) 11 | if err != nil { 12 | panic(err) 13 | } 14 | draw2d.RegisterFont(draw2d.FontData{ 15 | Name: "FreeMono", 16 | Family: draw2d.FontFamilyMono, 17 | Style: draw2d.FontStyleNormal, 18 | }, loadedFont) 19 | } 20 | -------------------------------------------------------------------------------- /ImageProcessor/ImageData/files/FreeMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/ImageProcessor/ImageData/files/FreeMono.ttf -------------------------------------------------------------------------------- /ImageProcessor/ImageData/files/ne_50m_admin_0_countries.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/ImageProcessor/ImageData/files/ne_50m_admin_0_countries.dbf -------------------------------------------------------------------------------- /ImageProcessor/ImageData/files/ne_50m_admin_0_countries.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]] -------------------------------------------------------------------------------- /ImageProcessor/ImageData/files/ne_50m_admin_0_countries.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/ImageProcessor/ImageData/files/ne_50m_admin_0_countries.shp -------------------------------------------------------------------------------- /ImageProcessor/ImageData/files/ne_50m_admin_0_countries.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/ImageProcessor/ImageData/files/ne_50m_admin_0_countries.shx -------------------------------------------------------------------------------- /ImageProcessor/ImageData/files/ne_50m_admin_1_states_provinces.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/ImageProcessor/ImageData/files/ne_50m_admin_1_states_provinces.dbf -------------------------------------------------------------------------------- /ImageProcessor/ImageData/files/ne_50m_admin_1_states_provinces.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /ImageProcessor/ImageData/files/ne_50m_admin_1_states_provinces.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/ImageProcessor/ImageData/files/ne_50m_admin_1_states_provinces.shp -------------------------------------------------------------------------------- /ImageProcessor/ImageData/files/ne_50m_admin_1_states_provinces.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/ImageProcessor/ImageData/files/ne_50m_admin_1_states_provinces.shx -------------------------------------------------------------------------------- /ImageProcessor/ImageData/files/wx-star.com_GOES-R_ABI_False-Color-LUT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/ImageProcessor/ImageData/files/wx-star.com_GOES-R_ABI_False-Color-LUT.png -------------------------------------------------------------------------------- /ImageProcessor/ImageProcessor.go: -------------------------------------------------------------------------------- 1 | package ImageProcessor 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor/ImageTools" 5 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor/MapCutter" 6 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor/MapDrawer" 7 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor/Structs" 8 | "github.com/opensatelliteproject/SatHelperApp/Logger" 9 | "github.com/opensatelliteproject/SatHelperApp/XRIT" 10 | "github.com/opensatelliteproject/SatHelperApp/XRIT/NOAAProductID" 11 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 12 | "sync" 13 | ) 14 | 15 | var purgeFiles = false 16 | 17 | type ImageProcessor struct { 18 | sync.Mutex 19 | MultiSegmentCache map[string]*Structs.MultiSegmentImage 20 | mapDrawer *MapDrawer.MapDrawer 21 | mapCutter *MapCutter.MapCutter 22 | reproject bool 23 | drawmap bool 24 | falsecolor bool 25 | enhance bool 26 | metadata bool 27 | cutRegions []string 28 | } 29 | 30 | func MakeImageProcessor() *ImageProcessor { 31 | return &ImageProcessor{ 32 | MultiSegmentCache: make(map[string]*Structs.MultiSegmentImage), 33 | mapDrawer: ImageTools.GetDefaultMapDrawer(), 34 | mapCutter: ImageTools.GetDefaultMapCutter(), 35 | reproject: false, 36 | drawmap: false, 37 | falsecolor: false, 38 | enhance: false, 39 | metadata: false, 40 | cutRegions: make([]string, 0), 41 | } 42 | } 43 | 44 | func (ip *ImageProcessor) SetFalseColor(fsclr bool) { 45 | ip.falsecolor = fsclr 46 | if fsclr { 47 | SLog.Warn("False color is enabled, so it will also save plain images with no map") 48 | ImageTools.SetSaveNoMap(true) // Needed for FSCLR 49 | } 50 | } 51 | 52 | func (ip *ImageProcessor) SetCutRegions(regions []string) { 53 | ip.cutRegions = regions 54 | } 55 | 56 | func (ip *ImageProcessor) SetDrawMap(drawMap bool) { 57 | ip.drawmap = drawMap 58 | } 59 | 60 | func (ip *ImageProcessor) SetReproject(reproject bool) { 61 | ip.reproject = reproject 62 | } 63 | 64 | func (ip *ImageProcessor) SetMetadata(metadata bool) { 65 | ip.metadata = metadata 66 | } 67 | 68 | func (ip *ImageProcessor) SetEnhance(enhance bool) { 69 | ip.enhance = enhance 70 | } 71 | 72 | func (ip *ImageProcessor) GetFalseColor() bool { 73 | return ip.falsecolor 74 | } 75 | 76 | func (ip *ImageProcessor) GetDrawMap() bool { 77 | return ip.drawmap 78 | } 79 | 80 | func (ip *ImageProcessor) GetReproject() bool { 81 | return ip.reproject 82 | } 83 | 84 | func (ip *ImageProcessor) GetEnhance() bool { 85 | return ip.enhance 86 | } 87 | 88 | func (ip *ImageProcessor) GetMetadata() bool { 89 | return ip.metadata 90 | } 91 | 92 | func (ip *ImageProcessor) GetCutRegions() []string { 93 | return ip.cutRegions 94 | } 95 | 96 | func (ip *ImageProcessor) GetMapCutter() *MapCutter.MapCutter { 97 | return ip.mapCutter 98 | } 99 | 100 | func (ip *ImageProcessor) GetMapDrawer() *MapDrawer.MapDrawer { 101 | if ip.drawmap { 102 | return ip.mapDrawer 103 | } 104 | 105 | return nil 106 | } 107 | 108 | func (ip *ImageProcessor) ProcessImage(filename string) { 109 | ip.Lock() 110 | defer ip.Unlock() 111 | 112 | xh, err := XRIT.ParseFile(filename) 113 | if err != nil { 114 | SLog.Error("Error parsing file %s: %s", filename, err) 115 | return 116 | } 117 | 118 | if xh.PrimaryHeader.FileTypeCode != PacketData.IMAGE { 119 | return 120 | } 121 | 122 | switch xh.NOAASpecificHeader.ProductID { 123 | case NOAAProductID.GOES16_ABI, NOAAProductID.GOES17_ABI: 124 | ProcessGOESABI(ip, filename, xh) 125 | } 126 | 127 | ip.checkExpired() 128 | } 129 | 130 | func (ip *ImageProcessor) checkExpired() { 131 | for k, v := range ip.MultiSegmentCache { 132 | if v.Expired() { 133 | SLog.Warn("Image %s timed out waiting segments. Removing from cache.", k) 134 | delete(ip.MultiSegmentCache, k) 135 | if purgeFiles { 136 | v.Purge() 137 | } 138 | } 139 | } 140 | } 141 | 142 | func SetPurgeFiles(purge bool) { 143 | purgeFiles = purge 144 | SLog.Info("Set Purge Files changed to %v", purge) 145 | } 146 | -------------------------------------------------------------------------------- /ImageProcessor/ImageTools/ColorReader.go: -------------------------------------------------------------------------------- 1 | package ImageTools 2 | 3 | import "image/color" 4 | 5 | type ColorReader interface { 6 | At(x, y int) color.Color 7 | } 8 | -------------------------------------------------------------------------------- /ImageProcessor/ImageTools/CurveManipulator.go: -------------------------------------------------------------------------------- 1 | package ImageTools 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "math" 7 | "reflect" 8 | "runtime" 9 | "sync" 10 | ) 11 | 12 | var defaultCurve = []float32{ 13 | 0.00000, 0.02576, 0.05148, 0.07712, 0.10264, 0.12799, 0.15313, 0.17803, 0.20264, 0.22692, 0.25083, 0.27432, 0.29737, 0.31991, 0.34193, 0.36336, 14 | 0.38418, 0.40433, 0.42379, 0.44250, 0.46043, 0.47754, 0.49378, 0.50911, 0.52350, 0.53690, 0.54926, 0.56055, 0.57073, 0.57976, 0.58984, 0.59659, 15 | 0.60321, 0.60969, 0.61604, 0.62226, 0.62835, 0.63432, 0.64016, 0.64588, 0.65147, 0.65694, 0.66230, 0.66754, 0.67267, 0.67768, 0.68258, 0.68738, 16 | 0.69206, 0.69664, 0.70112, 0.70549, 0.70976, 0.71394, 0.71802, 0.72200, 0.72589, 0.72968, 0.73339, 0.73701, 0.74055, 0.74399, 0.74736, 0.75065, 17 | 0.75385, 0.75698, 0.76003, 0.76301, 0.76592, 0.76875, 0.77152, 0.77422, 0.77686, 0.77943, 0.78194, 0.78439, 0.78679, 0.78912, 0.79140, 0.79363, 18 | 0.79581, 0.79794, 0.80002, 0.80206, 0.80405, 0.80600, 0.80791, 0.80978, 0.81162, 0.81342, 0.81518, 0.81692, 0.81862, 0.82030, 0.82195, 0.82358, 19 | 0.82518, 0.82676, 0.82833, 0.82987, 0.83140, 0.83292, 0.83442, 0.83592, 0.83740, 0.83888, 0.84036, 0.84183, 0.84329, 0.84476, 0.84623, 0.84771, 20 | 0.84919, 0.85068, 0.85217, 0.85368, 0.85520, 0.85674, 0.85829, 0.85986, 0.86145, 0.86306, 0.86469, 0.86635, 0.86803, 0.86974, 0.87149, 0.87326, 21 | 0.87500, 0.87681, 0.87861, 0.88038, 0.88214, 0.88388, 0.88560, 0.88730, 0.88898, 0.89064, 0.89228, 0.89391, 0.89552, 0.89711, 0.89868, 0.90023, 22 | 0.90177, 0.90329, 0.90479, 0.90627, 0.90774, 0.90919, 0.91063, 0.91205, 0.91345, 0.91483, 0.91620, 0.91756, 0.91890, 0.92022, 0.92153, 0.92282, 23 | 0.92410, 0.92536, 0.92661, 0.92784, 0.92906, 0.93027, 0.93146, 0.93263, 0.93380, 0.93495, 0.93608, 0.93720, 0.93831, 0.93941, 0.94050, 0.94157, 24 | 0.94263, 0.94367, 0.94471, 0.94573, 0.94674, 0.94774, 0.94872, 0.94970, 0.95066, 0.95162, 0.95256, 0.95349, 0.95441, 0.95532, 0.95622, 0.95711, 25 | 0.95799, 0.95886, 0.95973, 0.96058, 0.96142, 0.96225, 0.96307, 0.96389, 0.96469, 0.96549, 0.96628, 0.96706, 0.96783, 0.96860, 0.96936, 0.97010, 26 | 0.97085, 0.97158, 0.97231, 0.97303, 0.97374, 0.97445, 0.97515, 0.97584, 0.97653, 0.97721, 0.97789, 0.97856, 0.97922, 0.97988, 0.98053, 0.98118, 27 | 0.98182, 0.98246, 0.98309, 0.98372, 0.98435, 0.98497, 0.98559, 0.98620, 0.98681, 0.98741, 0.98802, 0.98862, 0.98921, 0.98980, 0.99039, 0.99098, 28 | 0.99157, 0.99215, 0.99273, 0.99331, 0.99389, 0.99446, 0.99503, 0.99561, 0.99618, 0.99675, 0.99732, 0.99788, 0.99845, 0.99902, 0.99959, 1.000000, 29 | } 30 | 31 | type CurveManipulator struct { 32 | cLut []byte 33 | } 34 | 35 | func MakeDefaultCurveManipulator() *CurveManipulator { 36 | _, cm := MakeCurveManipulator(defaultCurve) 37 | return cm 38 | } 39 | 40 | func MakeCurveManipulator(curve []float32) (error, *CurveManipulator) { 41 | if len(curve) != 256 { 42 | return fmt.Errorf("invalid curve size: %d. Expected 256", len(curve)), nil 43 | } 44 | 45 | cLut := make([]byte, 256) 46 | for i := 0; i < 256; i++ { 47 | cLut[i] = byte(math.Floor(float64(curve[i]) * 255)) 48 | } 49 | 50 | return nil, &CurveManipulator{ 51 | cLut: cLut, 52 | } 53 | } 54 | 55 | func (cm *CurveManipulator) ApplyCurve(img image.Image) error { 56 | switch v := img.(type) { 57 | case *image.RGBA: 58 | return cm.applyCurveRGBA(v) 59 | case *image.Gray: 60 | return cm.applyCurveGray(v) 61 | default: 62 | return fmt.Errorf("unsupported image type: %s", reflect.TypeOf(img).Name()) 63 | } 64 | } 65 | 66 | func (cm *CurveManipulator) applyCurveGray(img *image.Gray) error { 67 | cm.applyCurveParallel(img.Pix) 68 | return nil 69 | } 70 | 71 | func (cm *CurveManipulator) applyCurveRGBA(img *image.RGBA) error { 72 | cm.applyCurveParallel(img.Pix) 73 | return nil 74 | } 75 | 76 | func (cm *CurveManipulator) applyCurveParallel(data []byte) { 77 | wg := sync.WaitGroup{} 78 | n := runtime.NumCPU() 79 | if n > 4 { // Limit to 4 routines 80 | n = 4 81 | } 82 | t := len(data) 83 | w := t / n 84 | 85 | for i := 0; i < n; i++ { 86 | s := i * w 87 | e := (i + 1) * w 88 | if i+1 == n { 89 | e = len(data) 90 | } 91 | 92 | chunk := data[s:e] 93 | wg.Add(1) 94 | go func() { 95 | for i := 0; i < len(chunk); i++ { 96 | chunk[i] = cm.cLut[chunk[i]] 97 | } 98 | wg.Done() 99 | }() 100 | } 101 | 102 | wg.Wait() 103 | } 104 | -------------------------------------------------------------------------------- /ImageProcessor/ImageTools/Lut1D.go: -------------------------------------------------------------------------------- 1 | package ImageTools 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "image" 7 | "image/color" 8 | "image/draw" 9 | "io" 10 | "os" 11 | "reflect" 12 | ) 13 | 14 | type Lut1D struct { 15 | lut []color.Color 16 | } 17 | 18 | func MakeLut1D(filename string) (*Lut1D, error) { 19 | f, err := os.Open(filename) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | defer f.Close() 25 | 26 | return MakeLut1DFromReader(f) 27 | } 28 | 29 | func MakeLut1DFromMemory(data []byte) (*Lut1D, error) { 30 | return MakeLut1DFromReader(bytes.NewReader(data)) 31 | } 32 | 33 | func MakeLut1DFromColors(colors []color.Color) (*Lut1D, error) { 34 | if len(colors) != 256 { 35 | return nil, fmt.Errorf("invalid dimension %d. Expected 256", len(colors)) 36 | } 37 | 38 | l := &Lut1D{ 39 | lut: make([]color.Color, 256), 40 | } 41 | 42 | copy(l.lut, colors) 43 | 44 | return l, nil 45 | } 46 | 47 | func MakeLut1DFromReader(r io.Reader) (*Lut1D, error) { 48 | img, _, err := image.Decode(r) 49 | 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | b := img.Bounds() 55 | 56 | if b.Dx() != 256 || b.Dy() != 1 { 57 | return nil, fmt.Errorf("invalid dimensions %dx%d. Expected 256x1", b.Dx(), b.Dy()) 58 | } 59 | 60 | lut2d := &Lut1D{ 61 | lut: make([]color.Color, 256), 62 | } 63 | 64 | var cr ColorReader 65 | 66 | switch v := img.(type) { 67 | case *image.NRGBA: 68 | o := image.NewRGBA(v.Bounds()) 69 | draw.Draw(o, v.Bounds(), v, v.Bounds().Min, draw.Src) 70 | cr = o 71 | case *image.RGBA: 72 | cr = v 73 | case *image.Gray: 74 | cr = v 75 | default: 76 | return nil, fmt.Errorf("invalid image type: %s", reflect.TypeOf(img)) 77 | } 78 | 79 | for x := 0; x < 256; x++ { 80 | lut2d.lut[x] = cr.At(x, 0) 81 | } 82 | 83 | return lut2d, nil 84 | } 85 | 86 | func (l1d *Lut1D) ApplyFromGray(a *image.Gray) (*image.RGBA, error) { 87 | out := image.NewRGBA(a.Bounds()) 88 | 89 | s := a.Bounds() 90 | 91 | for y := 0; y < s.Dy(); y++ { 92 | for x := 0; x < s.Dx(); x++ { 93 | i := a.Pix[y*a.Stride+x] 94 | out.Set(x, y, l1d.lut[i]) 95 | } 96 | } 97 | 98 | return out, nil 99 | } 100 | 101 | func (l1d *Lut1D) ApplyFromRGBA(a *image.RGBA) (*image.RGBA, error) { 102 | out := image.NewRGBA(a.Bounds()) 103 | 104 | s := a.Bounds() 105 | 106 | for y := 0; y < s.Dy(); y++ { 107 | for x := 0; x < s.Dx(); x++ { 108 | i := int(a.Pix[y*a.Stride+x*4]) 109 | out.Set(x, y, l1d.lut[int(i)]) 110 | } 111 | } 112 | 113 | return out, nil 114 | } 115 | -------------------------------------------------------------------------------- /ImageProcessor/ImageTools/Lut2D.go: -------------------------------------------------------------------------------- 1 | package ImageTools 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "image" 7 | "image/color" 8 | "image/draw" 9 | "io" 10 | "os" 11 | "reflect" 12 | ) 13 | 14 | type Lut2D struct { 15 | lut [][]color.Color 16 | } 17 | 18 | func MakeLut2D(filename string) (*Lut2D, error) { 19 | f, err := os.Open(filename) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | defer f.Close() 25 | 26 | return MakeLut2DFromReader(f) 27 | } 28 | 29 | func MakeLut2DFromMemory(data []byte) (*Lut2D, error) { 30 | return MakeLut2DFromReader(bytes.NewReader(data)) 31 | } 32 | 33 | func MakeLut2DFromReader(r io.Reader) (*Lut2D, error) { 34 | img, _, err := image.Decode(r) 35 | 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | b := img.Bounds() 41 | 42 | if b.Dx() != 256 || b.Dy() != 256 { 43 | return nil, fmt.Errorf("invalid dimensions %dx%d. Expected 256x256", b.Dx(), b.Dy()) 44 | } 45 | 46 | lut2d := &Lut2D{ 47 | lut: make([][]color.Color, 256), 48 | } 49 | 50 | for i := 0; i < 256; i++ { 51 | lut2d.lut[i] = make([]color.Color, 256) 52 | } 53 | 54 | var cr ColorReader 55 | 56 | switch v := img.(type) { 57 | case *image.NRGBA: 58 | o := image.NewRGBA(v.Bounds()) 59 | draw.Draw(o, v.Bounds(), v, v.Bounds().Min, draw.Src) 60 | cr = o 61 | case *image.RGBA: 62 | cr = v 63 | case *image.Gray: 64 | cr = v 65 | default: 66 | return nil, fmt.Errorf("invalid image type: %s", reflect.TypeOf(img)) 67 | } 68 | 69 | for y := 0; y < 256; y++ { 70 | for x := 0; x < 256; x++ { 71 | lut2d.lut[y][x] = cr.At(x, y) 72 | } 73 | } 74 | 75 | return lut2d, nil 76 | } 77 | 78 | func (l2d *Lut2D) Apply(a, b *image.Gray) (*image.RGBA, error) { 79 | if !a.Bounds().Eq(b.Bounds()) { 80 | return nil, fmt.Errorf("the images does not have same size") 81 | } 82 | 83 | out := image.NewRGBA(a.Bounds()) 84 | 85 | s := a.Bounds() 86 | 87 | for y := 0; y < s.Dy(); y++ { 88 | for x := 0; x < s.Dx(); x++ { 89 | i := a.Pix[y*a.Stride+x] 90 | j := b.Pix[y*b.Stride+x] 91 | out.Set(x, y, l2d.lut[i][j]) 92 | } 93 | } 94 | 95 | return out, nil 96 | } 97 | -------------------------------------------------------------------------------- /ImageProcessor/MapDrawer/MapDrawer.go: -------------------------------------------------------------------------------- 1 | package MapDrawer 2 | 3 | import ( 4 | "bytes" 5 | "github.com/jonas-p/go-shp" 6 | "github.com/llgcode/draw2d/draw2dimg" 7 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor/Projector" 8 | "image" 9 | "image/color" 10 | "math" 11 | ) 12 | 13 | const defaultLineWidth = 3 14 | 15 | //var defaultLineColor = color.RGBA{ 16 | // R: 120, 17 | // G: 120, 18 | // B: 120, 19 | // A: 255, 20 | //} 21 | 22 | var defaultLineColor = color.RGBA{ 23 | R: 200, 24 | G: 120, 25 | B: 120, 26 | A: 255, 27 | } 28 | 29 | type MapDrawer struct { 30 | sections []*MapSection 31 | cache map[string][]*MapSection 32 | lineWidth float64 33 | lineColor color.Color 34 | } 35 | 36 | type MapSection struct { 37 | name string 38 | polygons []shp.Polygon 39 | fields map[string]string 40 | } 41 | 42 | func MakeMapDrawer(shapeFile string) (error, *MapDrawer) { 43 | shape, err := shp.Open(shapeFile) 44 | if err != nil { 45 | return err, nil 46 | } 47 | defer shape.Close() 48 | 49 | md := &MapDrawer{ 50 | sections: make([]*MapSection, 0), 51 | cache: make(map[string][]*MapSection), 52 | lineWidth: defaultLineWidth, 53 | lineColor: defaultLineColor, 54 | } 55 | 56 | fields := shape.Fields() 57 | for shape.Next() { 58 | n, p := shape.Shape() 59 | 60 | var poly *shp.Polygon 61 | 62 | switch v := p.(type) { 63 | case *shp.Polygon: 64 | poly = v 65 | } 66 | 67 | if poly == nil { 68 | continue 69 | } 70 | 71 | // Let's split each polygon part into several polygons 72 | section := &MapSection{ 73 | polygons: make([]shp.Polygon, 0), 74 | fields: make(map[string]string), 75 | } 76 | 77 | if len(poly.Parts) > 1 { 78 | idx := poly.Parts[0] 79 | for i := 1; i < len(poly.Parts); i++ { 80 | p := shp.Polygon{ 81 | Parts: make([]int32, 1), 82 | Points: poly.Points[idx:poly.Parts[i]], 83 | NumParts: 1, 84 | } 85 | p.NumPoints = int32(len(p.Points)) 86 | 87 | idx = poly.Parts[i] 88 | section.polygons = append(section.polygons, p) 89 | } 90 | // Last Part 91 | p := shp.Polygon{ 92 | Parts: make([]int32, 1), 93 | Points: poly.Points[idx:], 94 | NumParts: 1, 95 | } 96 | p.NumPoints = int32(len(p.Points)) 97 | section.polygons = append(section.polygons, p) 98 | } else { 99 | section.polygons = append(section.polygons, *poly) 100 | } 101 | 102 | for k, f := range fields { 103 | fieldBytes := f.Name[:] 104 | 105 | a := bytes.Split(fieldBytes, []byte{0}) 106 | 107 | field := string(a[0]) 108 | 109 | val := shape.ReadAttribute(n, k) 110 | section.fields[field] = val 111 | if field == "name" { 112 | section.name = val 113 | } 114 | } 115 | 116 | md.sections = append(md.sections, section) 117 | } 118 | 119 | return nil, md 120 | } 121 | 122 | func (md *MapDrawer) SetLineColor(c color.Color) { 123 | md.lineColor = c 124 | } 125 | 126 | func (md *MapDrawer) SetLineWidth(w float64) { 127 | md.lineWidth = w 128 | } 129 | 130 | func (md *MapDrawer) DrawMap(img *image.RGBA, gc Projector.ProjectionConverter) { 131 | w := float64(img.Bounds().Dx()) 132 | h := float64(img.Bounds().Dy()) 133 | draw := false 134 | 135 | ctx := draw2dimg.NewGraphicContext(img) 136 | ctx.SetStrokeColor(md.lineColor) 137 | ctx.SetLineWidth(md.lineWidth) 138 | 139 | for _, v := range md.sections { 140 | for _, poly := range v.polygons { 141 | p0 := poly.Points[0] 142 | lastX, lastY := gc.LatLon2XYf(p0.Y, p0.X) 143 | ctx.BeginPath() 144 | ctx.MoveTo(lastX, lastY) 145 | 146 | for _, p := range poly.Points { 147 | lat := p.Y 148 | lon := p.X 149 | if lat < gc.MaxLatitude() && lat > gc.MinLatitude() && lon < gc.MaxLongitude() && lon > gc.MinLongitude() { 150 | x, y := gc.LatLon2XYf(lat, lon) 151 | 152 | cx := float64(x) 153 | cy := float64(y) 154 | 155 | if (!math.IsNaN(lastX) && !math.IsNaN(lastY)) && 156 | (x > 0 && y > 0) && 157 | (cx < w && cy < h) && 158 | (lastX > 0 && lastY > 0) && 159 | (lastX < w && lastY < h) { 160 | 161 | if cx != lastX && cy != lastY { 162 | ctx.LineTo(cx, cy) 163 | } 164 | draw = true 165 | } 166 | lastX = cx 167 | lastY = cy 168 | } 169 | } 170 | if draw { 171 | ctx.Close() 172 | ctx.Stroke() 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /ImageProcessor/PlainLRITImage.go: -------------------------------------------------------------------------------- 1 | package ImageProcessor 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor/ImageTools" 5 | "github.com/opensatelliteproject/SatHelperApp/Logger" 6 | "github.com/opensatelliteproject/SatHelperApp/XRIT" 7 | "os" 8 | ) 9 | 10 | func PlainLRITImage(_ *ImageProcessor, filename string, _ *XRIT.Header) { 11 | // Plain images we just need to dump to jpeg. 12 | err := ImageTools.DumpImage(filename) 13 | if err != nil { 14 | SLog.Error("Error processing %s: %s", filename, err) 15 | } 16 | 17 | if purgeFiles { 18 | err = os.Remove(filename) 19 | if err != nil { 20 | SLog.Error("Error erasing %s: %s", filename, err) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ImageProcessor/Projector/LinearProjectionConverter.go: -------------------------------------------------------------------------------- 1 | package Projector 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | ) 7 | 8 | // LinearConverter 9 | // This is used for pixel map to coordinates from a reprojected image. 10 | type LinearConverter struct { 11 | minLat float64 12 | minLon float64 13 | maxLat float64 14 | maxLon float64 15 | 16 | imgWidth int 17 | imgHeight int 18 | } 19 | 20 | // MakeLinearConverter Creates a new instance of LinearConverter 21 | // This is used for pixel map to coordinates from a reprojected image. 22 | // imgWidth => Image Width in pixels 23 | // imgHeight => Image Height in pixels 24 | // pc => Previous Projection Converter (before reprojection) 25 | func MakeLinearConverter(imgWidth, imgHeight int, pc ProjectionConverter) ProjectionConverter { 26 | return &LinearConverter{ 27 | minLat: pc.MinLatitude(), 28 | maxLat: pc.MaxLatitude(), 29 | minLon: pc.MinLongitude(), 30 | maxLon: pc.MaxLongitude(), 31 | imgWidth: imgWidth, 32 | imgHeight: imgHeight, 33 | } 34 | } 35 | 36 | // LatLon2XY Converts Latitude/Longitude to Pixel X/Y 37 | // lat => Latitude in Degrees 38 | // lon => Longitude in Degrees 39 | func (gc *LinearConverter) LatLon2XY(lat, lon float64) (x, y int) { 40 | xf, yf := gc.LatLon2XYf(lat, lon) 41 | 42 | x = int(xf) 43 | y = int(yf) 44 | 45 | return 46 | } 47 | 48 | // LatLon2XYf Converts Latitude/Longitude to Pixel X/Y (float64) 49 | // lat => Latitude in Degrees 50 | // lon => Longitude in Degrees 51 | func (gc *LinearConverter) LatLon2XYf(lat, lon float64) (x, y float64) { 52 | nLat := -((lat - gc.maxLat + gc.TrimLatitude()) / (gc.LatitudeCoverage() - gc.TrimLatitude()*2)) 53 | nLon := (lon - gc.minLon - gc.TrimLongitude()) / (gc.LongitudeCoverage() - gc.TrimLongitude()*2) 54 | 55 | x = nLon * float64(gc.imgWidth) 56 | y = nLat * float64(gc.imgHeight) 57 | 58 | if x < 0 { 59 | x = 0 60 | } 61 | if x > float64(gc.imgWidth) { 62 | x = float64(gc.imgWidth) 63 | } 64 | 65 | if y < 0 { 66 | y = 0 67 | } 68 | 69 | if y > float64(gc.imgHeight) { 70 | y = float64(gc.imgHeight) 71 | } 72 | 73 | return 74 | } 75 | 76 | // XY2LatLon Converts Pixel X/Y to Latitude/Longitude 77 | // lat => Latitude in Degrees 78 | // lon => Longitude in Degrees 79 | func (gc *LinearConverter) XY2LatLon(x, y int) (lat, lon float64) { 80 | nX := float64(x) / float64(gc.imgWidth) 81 | nY := float64(y) / float64(gc.imgHeight) 82 | 83 | lat = (gc.MaxLatitude() - gc.TrimLatitude()) - (nY * (gc.LatitudeCoverage() - gc.TrimLatitude()*2)) 84 | lon = (nX * (gc.LongitudeCoverage() - gc.TrimLongitude()*2)) + (gc.MinLongitude() + gc.TrimLongitude()) 85 | 86 | return 87 | } 88 | 89 | // region Getters 90 | 91 | // ColumnOffset returns the number of pixels that the image is offset from left 92 | func (gc *LinearConverter) ColumnOffset() int { 93 | return 0 94 | } 95 | 96 | // LineOffset returns the number of pixels that the image is offset from top 97 | func (gc *LinearConverter) LineOffset() int { 98 | return 0 99 | } 100 | 101 | // CropLeft returns the number of pixels that should be cropped 102 | func (gc *LinearConverter) CropLeft() int { 103 | return 0 104 | } 105 | 106 | // MaxLatitude returns the Maximum Visible Latitude 107 | func (gc *LinearConverter) MaxLatitude() float64 { 108 | return gc.maxLat 109 | } 110 | 111 | // MinLatitude returns Minimum Visible Latitude 112 | func (gc *LinearConverter) MinLatitude() float64 { 113 | return gc.minLat 114 | } 115 | 116 | // MaxLongitude returns Maximum visible Longitude 117 | func (gc *LinearConverter) MaxLongitude() float64 { 118 | return gc.maxLon 119 | } 120 | 121 | // MinLongitude returns Minimum visible latitude 122 | func (gc *LinearConverter) MinLongitude() float64 { 123 | return gc.minLon 124 | } 125 | 126 | // LatitudeCoverage returns Coverage of the view in Latitude Degrees 127 | func (gc *LinearConverter) LatitudeCoverage() float64 { 128 | return gc.MaxLatitude() - gc.MinLatitude() 129 | } 130 | 131 | // LongitudeCoverage returns Coverage of the view in Longitude Degrees 132 | func (gc *LinearConverter) LongitudeCoverage() float64 { 133 | return gc.MaxLongitude() - gc.MinLongitude() 134 | } 135 | 136 | // TrimLongitude returns Longitude Trim parameter for removing artifacts on Reprojection (in degrees) 137 | func (gc *LinearConverter) TrimLongitude() float64 { 138 | return 16 139 | } 140 | 141 | // TrimLatitude returns Latitude Trim parameter for removing artifacts on Reprojection (in degrees) 142 | func (gc *LinearConverter) TrimLatitude() float64 { 143 | return 16 144 | } 145 | 146 | func (gc *LinearConverter) Hash() string { 147 | s := fmt.Sprintf("LinearConverter%f%f%f%f%d%d", gc.maxLon, gc.minLon, gc.maxLat, gc.minLat, gc.imgWidth, gc.imgHeight) 148 | h := sha256.New() 149 | _, _ = h.Write([]byte(s)) 150 | 151 | return fmt.Sprintf("%x", h.Sum(nil)) 152 | } 153 | 154 | // endregion 155 | -------------------------------------------------------------------------------- /ImageProcessor/Projector/Projection.go: -------------------------------------------------------------------------------- 1 | package Projector 2 | 3 | type ProjectionConverter interface { 4 | // LatLon2XY Converts Latitude/Longitude to Pixel X/Y 5 | // lat => Latitude in Degrees 6 | // lon => Longitude in Degrees 7 | LatLon2XY(lat, lon float64) (x, y int) 8 | 9 | // LatLon2XYf Converts Latitude/Longitude to Pixel X/Y (float64) 10 | // lat => Latitude in Degrees 11 | // lon => Longitude in Degrees 12 | LatLon2XYf(lat, lon float64) (x, y float64) 13 | 14 | // XY2LatLon Converts Pixel X/Y to Latitude/Longitude 15 | // lat => Latitude in Degrees 16 | // lon => Longitude in Degrees 17 | XY2LatLon(x, y int) (lat, lon float64) 18 | 19 | Hash() string 20 | 21 | // ColumnOffset returns the number of pixels that the image is offset from left 22 | ColumnOffset() int 23 | 24 | // LineOffset returns the number of pixels that the image is offset from top 25 | LineOffset() int 26 | 27 | // CropLeft returns the number of pixels that should be cropped 28 | CropLeft() int 29 | 30 | // MaxLatitude returns the Maximum Visible Latitude 31 | MaxLatitude() float64 32 | 33 | // MinLatitude returns Minimum Visible Latitude 34 | MinLatitude() float64 35 | 36 | // MaxLongitude returns Maximum visible Longitude 37 | MaxLongitude() float64 38 | 39 | // MinLongitude returns Minimum visible latitude 40 | MinLongitude() float64 41 | 42 | // LatitudeCoverage returns Coverage of the view in Latitude Degrees 43 | LatitudeCoverage() float64 44 | 45 | // LongitudeCoverage returns Coverage of the view in Longitude Degrees 46 | LongitudeCoverage() float64 47 | 48 | // TrimLongitude returns Longitude Trim parameter for removing artifacts on Reprojection (in degrees) 49 | TrimLongitude() float64 50 | 51 | // TrimLatitude returns Latitude Trim parameter for removing artifacts on Reprojection (in degrees) 52 | TrimLatitude() float64 53 | } 54 | -------------------------------------------------------------------------------- /ImageProcessor/Projector/Projector.go: -------------------------------------------------------------------------------- 1 | package Projector 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "math" 7 | "runtime" 8 | "sync" 9 | ) 10 | 11 | type Projector struct { 12 | gc ProjectionConverter 13 | } 14 | 15 | func MakeProjector(gc ProjectionConverter) *Projector { 16 | return &Projector{ 17 | gc: gc, 18 | } 19 | } 20 | 21 | func (p *Projector) DrawLatLonLines(src *image.RGBA, thickness int, c color.Color) { 22 | t0 := -thickness / 2 23 | t1 := thickness - t0 24 | 25 | for lat := p.gc.MinLatitude(); lat < p.gc.MaxLatitude(); lat += 10 { 26 | for lon := p.gc.MinLongitude(); lon < p.gc.MaxLongitude(); lon += 0.01 { 27 | x, y := p.gc.LatLon2XY(lat, lon) 28 | for i := -t0; i < t1; i++ { 29 | src.Set(x, y+i, c) 30 | } 31 | } 32 | } 33 | 34 | for lon := p.gc.MinLongitude(); lon < p.gc.MaxLongitude(); lon += 10 { 35 | for lat := p.gc.MinLatitude(); lat < p.gc.MaxLatitude(); lat += 0.01 { 36 | x, y := p.gc.LatLon2XY(lat, lon) 37 | for i := -t0; i < t1; i++ { 38 | src.Set(x, y+i, c) 39 | } 40 | } 41 | } 42 | } 43 | 44 | func (p *Projector) ReprojectLinearGray(src *image.Gray) *image.Gray { 45 | dst := image.NewGray(src.Bounds()) 46 | sampler := MakeSampler2D(src) 47 | width := src.Bounds().Dx() 48 | height := src.Bounds().Dy() 49 | 50 | p.reprojectChunkGray(dst, sampler, 0, 0, width, height) 51 | 52 | return dst 53 | } 54 | 55 | func (p *Projector) ReprojectLinear(src *image.RGBA) *image.RGBA { 56 | dst := image.NewRGBA(src.Bounds()) 57 | sampler := MakeSampler2D(src) 58 | width := src.Bounds().Dx() 59 | height := src.Bounds().Dy() 60 | 61 | p.reprojectChunk(dst, sampler, 0, 0, width, height) 62 | 63 | return dst 64 | } 65 | 66 | func (p *Projector) ReprojectLinearMultiThread(src image.Image) *image.RGBA { 67 | dst := image.NewRGBA(src.Bounds()) 68 | sampler := MakeSampler2D(src) 69 | width := src.Bounds().Dx() 70 | height := src.Bounds().Dy() 71 | 72 | n := runtime.NumCPU() / 3 // Let's not use ALL available stuff since that can break other stuff 73 | n = int(math.Round(float64(n))) 74 | if n < 1 { 75 | n = 1 76 | } 77 | 78 | n2 := int(math.Sqrt(float64(n))) 79 | 80 | nX := n2 81 | nY := n - n2 82 | 83 | if nY == 0 { 84 | nY = 1 // Single Thread 85 | } 86 | 87 | wg := sync.WaitGroup{} 88 | 89 | deltaY := height / nY 90 | deltaX := width / nX 91 | 92 | for i := 0; i < nX; i++ { 93 | sx := i * deltaX 94 | ex := (i + 1) * deltaX 95 | if ex > width || (i+1 == nX && ex != width) { 96 | ex = width 97 | } 98 | 99 | for j := 0; j < nY; j++ { 100 | sy := j * deltaY 101 | ey := (j + 1) * deltaY 102 | 103 | if ey > height || (j+1 == nY && ey != height) { 104 | ey = height 105 | } 106 | 107 | wg.Add(1) 108 | go func() { 109 | defer wg.Done() 110 | p.reprojectChunk(dst, sampler, sx, sy, ex, ey) 111 | }() 112 | } 113 | } 114 | wg.Wait() 115 | 116 | return dst 117 | } 118 | 119 | func (p *Projector) ReprojectLinearMultiThreadGray(src image.Image) *image.Gray { 120 | dst := image.NewGray(src.Bounds()) 121 | sampler := MakeSampler2D(src) 122 | width := src.Bounds().Dx() 123 | height := src.Bounds().Dy() 124 | 125 | n := runtime.NumCPU() 126 | n2 := int(math.Sqrt(float64(n))) 127 | 128 | nX := n2 129 | nY := n - n2 130 | 131 | if nY == 0 { 132 | nY = 1 // Single Thread 133 | } 134 | 135 | wg := sync.WaitGroup{} 136 | 137 | deltaY := height / nY 138 | deltaX := width / nX 139 | 140 | for i := 0; i < nX; i++ { 141 | sx := i * deltaX 142 | ex := (i + 1) * deltaX 143 | if ex > width || (i+1 == nX && ex != width) { 144 | ex = width 145 | } 146 | 147 | for j := 0; j < nY; j++ { 148 | sy := j * deltaY 149 | ey := (j + 1) * deltaY 150 | 151 | if ey > height || (j+1 == nY && ey != height) { 152 | ey = height 153 | } 154 | 155 | wg.Add(1) 156 | go func() { 157 | defer wg.Done() 158 | p.reprojectChunkGray(dst, sampler, sx, sy, ex, ey) 159 | }() 160 | } 161 | } 162 | wg.Wait() 163 | 164 | return dst 165 | } 166 | 167 | func (p *Projector) reprojectChunk(dst *image.RGBA, sampler *Sampler2D, sx, sy, ex, ey int) { 168 | dPtr := dst.Pix 169 | stride := dst.Stride 170 | width := dst.Bounds().Dx() 171 | height := dst.Bounds().Dy() 172 | 173 | for y := sy; y < ey; y++ { 174 | for x := sx; x < ex; x++ { 175 | lat := (p.gc.MaxLatitude() - p.gc.TrimLatitude()) - ((float64(y) * (p.gc.LatitudeCoverage() - p.gc.TrimLatitude()*2)) / float64(height)) 176 | lon := ((float64(x) * (p.gc.LongitudeCoverage() - p.gc.TrimLongitude()*2)) / float64(width)) + (p.gc.MinLongitude() + p.gc.TrimLongitude()) 177 | 178 | if lat > p.gc.MaxLatitude() || lat < p.gc.MinLatitude() || lon > p.gc.MaxLongitude() || lon < p.gc.MinLongitude() { 179 | dPtr[y*stride+x] = 0 180 | dPtr[y*stride+x+1] = 0 181 | dPtr[y*stride+x+2] = 0 182 | dPtr[y*stride+x+3] = 255 183 | } else { 184 | tx, ty := p.gc.LatLon2XYf(lat, lon) 185 | c := sampler.GetPixel(tx, ty) 186 | 187 | r, g, b, a := c.RGBA() 188 | 189 | dPtr[y*stride+x*4+0] = uint8(r) 190 | dPtr[y*stride+x*4+1] = uint8(g) 191 | dPtr[y*stride+x*4+2] = uint8(b) 192 | dPtr[y*stride+x*4+3] = uint8(a) 193 | } 194 | } 195 | } 196 | } 197 | 198 | func (p *Projector) reprojectChunkGray(dst *image.Gray, sampler *Sampler2D, sx, sy, ex, ey int) { 199 | dPtr := dst.Pix 200 | stride := dst.Stride 201 | width := dst.Bounds().Dx() 202 | height := dst.Bounds().Dy() 203 | 204 | for y := sy; y < ey; y++ { 205 | for x := sx; x < ex; x++ { 206 | lat := (p.gc.MaxLatitude() - p.gc.TrimLatitude()) - ((float64(y) * (p.gc.LatitudeCoverage() - p.gc.TrimLatitude()*2)) / float64(height)) 207 | lon := ((float64(x) * (p.gc.LongitudeCoverage() - p.gc.TrimLongitude()*2)) / float64(width)) + (p.gc.MinLongitude() + p.gc.TrimLongitude()) 208 | 209 | if lat > p.gc.MaxLatitude() || lat < p.gc.MinLatitude() || lon > p.gc.MaxLongitude() || lon < p.gc.MinLongitude() { 210 | dPtr[y*stride+x] = 0 211 | } else { 212 | tx, ty := p.gc.LatLon2XYf(lat, lon) 213 | c := sampler.GetPixelGray(tx, ty) 214 | 215 | dPtr[y*stride+x] = uint8(c.Y) 216 | } 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /ImageProcessor/Projector/Sampler.go: -------------------------------------------------------------------------------- 1 | package Projector 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | ) 7 | 8 | type Sampler2D struct { 9 | numChannels int 10 | stride int 11 | pixData []byte 12 | } 13 | 14 | func MakeSampler2D(img image.Image) *Sampler2D { 15 | o := &Sampler2D{} 16 | 17 | switch v := img.(type) { 18 | case *image.RGBA: 19 | o.numChannels = 4 20 | o.pixData = v.Pix 21 | o.stride = v.Stride 22 | case *image.Gray: 23 | o.numChannels = 1 24 | o.pixData = v.Pix 25 | o.stride = v.Stride 26 | default: 27 | panic("image type not supported") 28 | } 29 | 30 | return o 31 | } 32 | 33 | func (s *Sampler2D) GetPixelGray(x, y float64) color.Gray { 34 | return color.Gray{ 35 | Y: BilinearInterp(s.pixData, x, y, s.stride, s.numChannels, 0), 36 | } 37 | } 38 | 39 | func (s *Sampler2D) GetPixel(x, y float64) color.Color { 40 | var r, g, b, a byte 41 | 42 | if x < 0 || y < 0 { 43 | return color.Black 44 | } 45 | 46 | r = BilinearInterp(s.pixData, x, y, s.stride, s.numChannels, 0) 47 | a = 255 48 | 49 | switch s.numChannels { 50 | case 1: 51 | g = r 52 | b = r 53 | case 3: 54 | g = BilinearInterp(s.pixData, x, y, s.stride, s.numChannels, 1) 55 | b = BilinearInterp(s.pixData, x, y, s.stride, s.numChannels, 2) 56 | case 4: 57 | g = BilinearInterp(s.pixData, x, y, s.stride, s.numChannels, 1) 58 | b = BilinearInterp(s.pixData, x, y, s.stride, s.numChannels, 2) 59 | a = BilinearInterp(s.pixData, x, y, s.stride, s.numChannels, 3) 60 | } 61 | 62 | return color.RGBA{ 63 | R: r, 64 | G: g, 65 | B: b, 66 | A: a, 67 | } 68 | } 69 | 70 | func BilinearInterp(data []byte, x, y float64, mw int, numChannels, colorChannel int) byte { 71 | rx := int(x) 72 | ry := int(y) 73 | fracX := x - float64(rx) 74 | fracY := y - float64(ry) 75 | 76 | if fracX == 0 && fracY == 0 { // Integer amount 77 | return valueAtImage(data, rx, ry, mw, numChannels, colorChannel) 78 | } 79 | 80 | invFracX := 1 - fracX 81 | invFracY := 1 - fracY 82 | 83 | a := valueAtImageF(data, rx, ry, mw, numChannels, colorChannel) 84 | b := valueAtImageF(data, rx+1, ry, mw, numChannels, colorChannel) 85 | c := valueAtImageF(data, rx, ry+1, mw, numChannels, colorChannel) 86 | d := valueAtImageF(data, rx+1, ry+1, mw, numChannels, colorChannel) 87 | 88 | v := (a*invFracX+b*fracX)*invFracY + (c*invFracX+d*fracX)*fracY 89 | 90 | return byte(v) 91 | } 92 | 93 | func valueAtImageF(data []byte, x, y, mw, numChannels, colorChannel int) float64 { 94 | return float64(valueAtImage(data, x, y, mw, numChannels, colorChannel)) 95 | } 96 | 97 | func valueAtImage(data []byte, x, y, mw, numChannels, colorChannel int) byte { 98 | px := y*mw + x*numChannels + colorChannel 99 | if px >= len(data) { 100 | return 0 101 | } 102 | return data[px] 103 | } 104 | -------------------------------------------------------------------------------- /ImageProcessor/Structs/MultiSegmentImage.go: -------------------------------------------------------------------------------- 1 | package Structs 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/Logger" 5 | "github.com/opensatelliteproject/SatHelperApp/XRIT" 6 | "os" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | const imageTimeout = time.Minute * 60 // 1 hour timeout 12 | 13 | type MultiSegmentImage struct { 14 | SubProductId int 15 | ImageID int 16 | Name string 17 | Files []string 18 | MaxSegments int 19 | 20 | CreatedAt time.Time 21 | 22 | FirstSegmentHeader *XRIT.Header 23 | FirstSegmentFilename string 24 | 25 | lock sync.Mutex 26 | } 27 | 28 | func StringIndexOf(v string, a []string) int { 29 | for i, vo := range a { 30 | if vo == v { 31 | return i 32 | } 33 | } 34 | 35 | return -1 36 | } 37 | 38 | func MakeMultiSegmentImage(Name string, SubProductId, ImageID int) *MultiSegmentImage { 39 | return &MultiSegmentImage{ 40 | Name: Name, 41 | SubProductId: SubProductId, 42 | ImageID: ImageID, 43 | CreatedAt: time.Now(), 44 | MaxSegments: -1, 45 | Files: make([]string, 0), 46 | lock: sync.Mutex{}, 47 | FirstSegmentHeader: nil, 48 | } 49 | } 50 | 51 | func (msi *MultiSegmentImage) Expired() bool { 52 | msi.lock.Lock() 53 | defer msi.lock.Unlock() 54 | 55 | return time.Since(msi.CreatedAt) > imageTimeout 56 | } 57 | 58 | func (msi *MultiSegmentImage) PutSegment(filename string, xh *XRIT.Header) { 59 | msi.lock.Lock() 60 | defer msi.lock.Unlock() 61 | 62 | if xh.SegmentIdentificationHeader == nil { 63 | SLog.Error("No Segment Identification Header for segment!") 64 | return 65 | } 66 | 67 | if msi.MaxSegments == -1 { 68 | msi.MaxSegments = int(xh.SegmentIdentificationHeader.MaxSegments) 69 | } 70 | 71 | if StringIndexOf(filename, msi.Files) == -1 { 72 | msi.Files = append(msi.Files, filename) 73 | } 74 | 75 | if msi.FirstSegmentHeader == nil || xh.SegmentIdentificationHeader.Sequence < msi.FirstSegmentHeader.SegmentIdentificationHeader.Sequence { 76 | msi.FirstSegmentHeader = xh 77 | msi.FirstSegmentFilename = filename 78 | } 79 | } 80 | 81 | func (msi *MultiSegmentImage) Done() bool { 82 | msi.lock.Lock() 83 | defer msi.lock.Unlock() 84 | 85 | return len(msi.Files) == msi.MaxSegments 86 | } 87 | 88 | func (msi *MultiSegmentImage) Purge() { 89 | for _, v := range msi.Files { 90 | SLog.Debug("Removing %s", v) 91 | err := os.Remove(v) 92 | if err != nil { 93 | SLog.Error("Error erasing %s: %s", v, err) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Open Satellite Project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Logger/LogManager.go: -------------------------------------------------------------------------------- 1 | package SLog 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/logrusorgru/aurora" 7 | "github.com/mitchellh/go-homedir" 8 | "log" 9 | "os" 10 | "sync" 11 | ) 12 | 13 | var displayOnTermUI = false 14 | 15 | var logStarted = false 16 | var logPath string 17 | var logPathFlag = flag.String("logdir", "", "Log folder") 18 | var logFileHandle *os.File 19 | var logMtx = sync.Mutex{} 20 | 21 | func StartLog() { 22 | if !logStarted { 23 | flag.Parse() 24 | home, _ := homedir.Dir() 25 | logPath = fmt.Sprintf("%s/SatHelperApp/logs", home) 26 | if *logPathFlag != "" { 27 | logPath = *logPathFlag 28 | } 29 | logStarted = true 30 | err := os.MkdirAll(logPath, os.ModePerm) 31 | if err != nil { 32 | panic(err) 33 | } 34 | file, err := os.Create(fmt.Sprintf("%s/log.txt", logPath)) 35 | if err != nil { 36 | Error("Error opening log file: %s. Won't try again...", err) 37 | return 38 | } 39 | Info(aurora.Bold("Log Started at %s/log.txt . If no message apears please check the log.").String(), logPath) 40 | logFileHandle = file 41 | } 42 | } 43 | 44 | func EndLog() { 45 | logMtx.Lock() 46 | defer logMtx.Unlock() 47 | 48 | if logFileHandle != nil { 49 | logFileHandle.Close() 50 | } 51 | } 52 | 53 | func SetTermUiDisplay(b bool) { 54 | logMtx.Lock() 55 | defer logMtx.Unlock() 56 | displayOnTermUI = b 57 | } 58 | 59 | func Info(str string, v ...interface{}) { 60 | Log(str, v...) 61 | } 62 | 63 | func Log(str string, v ...interface{}) { 64 | logMtx.Lock() 65 | defer logMtx.Unlock() 66 | 67 | if displayOnTermUI { 68 | log.Printf("[I](fg-bold) [%s](fg-cyan)\n", fmt.Sprintf(str, v...)) 69 | } else { 70 | log.Printf(aurora.Cyan("%s").String(), fmt.Sprintf(str, v...)) 71 | } 72 | 73 | if logFileHandle != nil { 74 | _, err := logFileHandle.WriteString(aurora.Cyan(fmt.Sprintf("[I] %s\n", fmt.Sprintf(str, v...))).String()) 75 | if err != nil { 76 | log.Println(err) 77 | } 78 | } 79 | } 80 | 81 | func Debug(str string, v ...interface{}) { 82 | logMtx.Lock() 83 | defer logMtx.Unlock() 84 | 85 | if displayOnTermUI { 86 | log.Printf("[D](fg-bold) [%s](fg-magenta)\n", fmt.Sprintf(str, v...)) 87 | } else { 88 | log.Printf(aurora.Magenta("%s").String(), fmt.Sprintf(str, v...)) 89 | } 90 | if logFileHandle != nil { 91 | _, err := logFileHandle.WriteString(aurora.Magenta(fmt.Sprintf("[D] %s\n", fmt.Sprintf(str, v...))).String()) 92 | if err != nil { 93 | log.Println(err) 94 | } 95 | } 96 | } 97 | 98 | func Warn(str string, v ...interface{}) { 99 | logMtx.Lock() 100 | defer logMtx.Unlock() 101 | 102 | if displayOnTermUI { 103 | log.Printf("[W](fg-bold) [%s](fg-yellow)\n", fmt.Sprintf(str, v...)) 104 | } else { 105 | log.Printf(aurora.Brown("%s").String(), fmt.Sprintf(str, v...)) 106 | } 107 | if logFileHandle != nil { 108 | _, err := logFileHandle.WriteString(aurora.Brown(fmt.Sprintf("[W] %s\n", fmt.Sprintf(str, v...))).String()) 109 | if err != nil { 110 | log.Println(err) 111 | } 112 | } 113 | } 114 | 115 | func Error(str string, v ...interface{}) { 116 | logMtx.Lock() 117 | defer logMtx.Unlock() 118 | 119 | if displayOnTermUI { 120 | log.Printf("[E](fg-bold) [%s](fg-red)\n", fmt.Sprintf(str, v...)) 121 | } else { 122 | log.Printf(aurora.Red("%s").String(), fmt.Sprintf(str, v...)) 123 | } 124 | if logFileHandle != nil { 125 | _, err := logFileHandle.WriteString(aurora.Red(fmt.Sprintf("[E] %s\n", fmt.Sprintf(str, v...))).String()) 126 | if err != nil { 127 | log.Println(err) 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE := opensatelliteproject/SatHelperApp 2 | REV_VAR := github.com/opensatelliteproject/SatHelperApp.RevString 3 | VERSION_VAR := github.com/opensatelliteproject/SatHelperApp.VersionString 4 | BUILD_DATE_VAR := github.com/opensatelliteproject/SatHelperApp.CompilationDate 5 | BUILD_TIME_VAR := github.com/opensatelliteproject/SatHelperApp.CompilationTime 6 | REPO_VERSION := $(shell git describe --always --dirty --tags) 7 | REPO_REV := $(shell git rev-parse --sq HEAD) 8 | BUILD_DATE := $(shell date +"%b %d %Y") 9 | BUILD_TIME := $(shell date +"%H:%M:%S") 10 | 11 | PATH := $(PATH):/usr/lib/go-1.11/bin 12 | 13 | GOBIN := $(shell PATH=$PATH:/usr/lib/go-1.11/bin:/usr/local/Cellar/go/1.11.2/bin command -v go 2> /dev/null) 14 | BASEDIR := $(CURDIR) 15 | GOPATH := $(CURDIR)/.gopath 16 | BASE := $(GOPATH)/src/$(PACKAGE) 17 | DESTDIR?=/usr/local/bin 18 | GOBUILD_VERSION_ARGS := -ldflags "-X $(REV_VAR)=$(REPO_REV) -X $(VERSION_VAR)=$(REPO_VERSION) -X \"$(BUILD_DATE_VAR)=$(BUILD_DATE)\" -X $(BUILD_TIME_VAR)=$(BUILD_TIME)" 19 | 20 | INTSIZE := $(shell getconf LONG_BIT) 21 | 22 | .PHONY: all build 23 | .NOTPARALLEL: pre deps update 24 | 25 | all: | $(BASE) pre deps update build 26 | 27 | $(BASE): 28 | @echo Linking virtual GOPATH 29 | @mkdir -p $(dir $@) 30 | @ln -sf $(CURDIR) $@ 31 | 32 | pre: 33 | @echo Prechecking 34 | ifndef GOBIN 35 | $(error "GO executable not found") 36 | endif 37 | 38 | clean: 39 | @echo Cleaning virtual GOPATH 40 | @rm -fr .gopath 41 | 42 | deps: | $(BASE) 43 | @echo Downloading dependencies 44 | @cd $(BASE) && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) get 45 | @echo Deps for SatHelperApp 46 | @cd $(BASE)/cmd/SatHelperApp && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) get 47 | @echo Deps for DemuxReplay 48 | @cd $(BASE)/cmd/demuxReplay && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) get 49 | @echo Deps for xritparse 50 | @cd $(BASE)/cmd/xritparse && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) get 51 | @echo Deps for xritcat 52 | @cd $(BASE)/cmd/xritcat && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) get 53 | @echo Deps for xritimg 54 | @cd $(BASE)/cmd/xritimg && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) get 55 | @echo Deps for xritpdcs 56 | @cd $(BASE)/cmd/xritpdcs && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) get 57 | @echo Deps for MultiSegmentDump 58 | @cd $(BASE)/cmd/MultiSegmentDump && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) get 59 | @echo Deps for rpcClient 60 | @cd $(BASE)/cmd/rpcClient && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) get 61 | 62 | do-static: | $(BASE) 63 | @echo "Updating Code to have static libLimeSuite" 64 | @sed -i 's/-lLimeSuite/-l:libLimeSuite.a -l:libstdc++.a -static-libgcc -lm -lusb-1.0/g' $(GOPATH)/pkg/mod/github.com/myriadrf/limedrv*/limewrap/limewrap.go 65 | 66 | 67 | update: | do-static $(BASE) 68 | @echo Updating AirspyDevice Wrapper 69 | @cd $(BASE) && swig -cgo -go -c++ -intgosize $(INTSIZE) Frontend/AirspyDevice/AirspyDevice.i 70 | 71 | @echo Updating RTLSDR Wrappper 72 | @cd $(BASE) && swig -cgo -go -c++ -intgosize $(INTSIZE) Frontend/RTLSDRDevice/RTLSDRDevice.i 73 | 74 | build: | $(BASE) 75 | @echo Building SatHelperApp 76 | @cd $(BASE)/cmd/SatHelperApp && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) build $(GOBUILD_VERSION_ARGS) -o $(BASEDIR)/SatHelperApp 77 | 78 | @echo Building DemuxReplay 79 | @cd $(BASE)/cmd/demuxReplay && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) build $(GOBUILD_VERSION_ARGS) -o $(BASEDIR)/DemuxReplay 80 | 81 | @echo Building xritparse 82 | @cd $(BASE)/cmd/xritparse && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) build $(GOBUILD_VERSION_ARGS) -o $(BASEDIR)/xritparse 83 | 84 | @echo Building xritcat 85 | @cd $(BASE)/cmd/xritcat && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) build $(GOBUILD_VERSION_ARGS) -o $(BASEDIR)/xritcat 86 | 87 | @echo Building xritimg 88 | @cd $(BASE)/cmd/xritimg && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) build $(GOBUILD_VERSION_ARGS) -o $(BASEDIR)/xritimg 89 | 90 | @echo Building xritpdcs 91 | @cd $(BASE)/cmd/xritpdcs && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) build $(GOBUILD_VERSION_ARGS) -o $(BASEDIR)/xritpdcs 92 | 93 | @echo Building MultiSegmentDump 94 | @cd $(BASE)/cmd/MultiSegmentDump && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) build $(GOBUILD_VERSION_ARGS) -o $(BASEDIR)/SatHelperDump 95 | 96 | @echo Building rpcClient 97 | @cd $(BASE)/cmd/rpcClient && GO111MODULE=on GOPATH=$(GOPATH) $(GOBIN) build $(GOBUILD_VERSION_ARGS) -o $(BASEDIR)/SatHelperClient 98 | 99 | 100 | install: | $(BASE) 101 | @echo Installing 102 | @cd $(BASE) && cp $(BASEDIR)/SatHelperApp $(DESTDIR)/SatHelperApp 103 | @chmod +x $(DESTDIR)/SatHelperApp 104 | @cd $(BASE) && cp $(BASEDIR)/DemuxReplay $(DESTDIR)/DemuxReplay 105 | @chmod +x $(DESTDIR)/DemuxReplay 106 | @cd $(BASE) && cp $(BASEDIR)/xritparse $(DESTDIR)/xritparse 107 | @chmod +x $(DESTDIR)/xritparse 108 | @cd $(BASE) && cp $(BASEDIR)/xritcat $(DESTDIR)/xritcat 109 | @chmod +x $(DESTDIR)/xritcat 110 | @cd $(BASE) && cp $(BASEDIR)/xritimg $(DESTDIR)/xritimg 111 | @chmod +x $(DESTDIR)/xritimg 112 | @cd $(BASE) && cp $(BASEDIR)/xritpdcs $(DESTDIR)/xritpdcs 113 | @chmod +x $(DESTDIR)/xritpdcs 114 | @cd $(BASE) && cp $(BASEDIR)/SatHelperDump $(DESTDIR)/SatHelperDump 115 | @chmod +x $(DESTDIR)/SatHelperDump 116 | @cd $(BASE) && cp $(BASEDIR)/SatHelperClient $(DESTDIR)/SatHelperClient 117 | @chmod +x $(DESTDIR)/SatHelperClient 118 | 119 | test: 120 | go test -v -race $(shell go list ./... | grep -v /parts/ | grep -v /prime/ | grep -v /snap/ | grep -v /stage/ | grep -v /tmp/ | grep -v /librtlsdr/ ) 121 | -------------------------------------------------------------------------------- /Models/Config.go: -------------------------------------------------------------------------------- 1 | package Models 2 | 3 | // region Config File Structs 4 | type BaseConfig struct { 5 | SymbolRate uint32 6 | RRCAlpha float32 7 | Mode string 8 | Decimation uint8 9 | AGCEnabled bool 10 | DeviceType string 11 | SendConstellation bool 12 | PLLAlpha float32 13 | DemuxerType string 14 | StatisticsPort int 15 | } 16 | 17 | type CFileSourceConfig struct { 18 | Filename string 19 | FastAsPossible bool 20 | } 21 | 22 | type AirspySourceConfig struct { 23 | MixerGain uint8 24 | LNAGain uint8 25 | VGAGain uint8 26 | BiasTEnabled bool 27 | } 28 | 29 | type RTLSDRSourceConfig struct { 30 | MixerGain uint8 31 | LNAGain uint8 32 | VGAGain uint8 33 | BiasTEnabled bool 34 | } 35 | 36 | type LimeSourceConfig struct { 37 | LNAGain uint8 38 | Antenna string 39 | } 40 | 41 | type SpyserverSourceConfig struct { 42 | Gain uint8 43 | Hostname string 44 | Port int 45 | } 46 | 47 | type SourceConfig struct { 48 | SampleRate uint32 49 | Frequency uint32 50 | } 51 | 52 | type DecoderConfig struct { 53 | Display bool 54 | UseLastFrameData bool 55 | } 56 | 57 | type TCPServerDemuxerConfig struct { 58 | Port int 59 | Host string 60 | } 61 | 62 | type FileDemuxerConfig struct { 63 | Filename string 64 | } 65 | 66 | type DirectDemuxerConfig struct { 67 | OutputFolder string 68 | TemporaryFolder string 69 | PurgeFilesAfterProcess bool 70 | SkipVCID []int 71 | ReprojectImages bool 72 | DrawMap bool 73 | FalseColor bool 74 | Enhanced bool 75 | MetaFrame bool 76 | } 77 | 78 | type RPC struct { 79 | Enable bool 80 | ListenPort int 81 | ListenAddr string 82 | } 83 | 84 | type Prometheus struct { 85 | Enable bool 86 | ListenPort int 87 | ListenAddr string 88 | } 89 | 90 | type AppConfig struct { 91 | Title string 92 | Base BaseConfig 93 | Decoder DecoderConfig 94 | Source SourceConfig 95 | AirspySource AirspySourceConfig 96 | LimeSource LimeSourceConfig 97 | CFileSource CFileSourceConfig 98 | TCPServerDemuxer TCPServerDemuxerConfig 99 | FileDemuxer FileDemuxerConfig 100 | SpyserverSource SpyserverSourceConfig 101 | DirectDemuxer DirectDemuxerConfig 102 | RtlsdrSource RTLSDRSourceConfig 103 | RPC RPC 104 | Prometheus Prometheus 105 | } 106 | 107 | // endregion 108 | -------------------------------------------------------------------------------- /Models/Statistics.go: -------------------------------------------------------------------------------- 1 | package Models 2 | 3 | type Statistics struct { 4 | SCID uint8 5 | VCID uint8 6 | PacketNumber uint64 7 | VitErrors uint16 8 | FrameBits uint16 9 | RsErrors [4]int32 10 | SignalQuality uint8 11 | SyncCorrelation uint8 12 | PhaseCorrection uint8 13 | LostPackets uint64 14 | AverageVitCorrections uint16 15 | AverageRSCorrections uint8 16 | DroppedPackets uint64 17 | ReceivedPacketsPerChannel [256]int64 18 | LostPacketsPerChannel [256]int64 19 | TotalPackets uint64 20 | StartTime uint32 21 | SyncWord [4]uint8 22 | FrameLock uint8 23 | DemodulatorFifoUsage uint8 24 | DecoderFifoUsage uint8 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![SatHelperApp](https://snapcraft.io/sathelperapp/badge.svg)](https://snapcraft.io/sathelperapp) [![Build Status](https://api.travis-ci.org/opensatelliteproject/SatHelperApp.svg?branch=master)](https://travis-ci.org/opensatelliteproject/SatHelperApp) [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://tldrlegal.com/license/mit-license) 2 | 3 | 4 | SatHelperApp 5 | ============ 6 | 7 | The OpenSatelliteProject Satellite Helper Application! This is currently a LRIT/HRIT Demodulator / Decoder program based on [libSatHelper](https://github.com/opensatelliteproject/libsathelper/) and [xritdemod](https://github.com/opensatelliteproject/xritdemod). 8 | 9 | It is currently WIP and in Alpha State. Use with care. 10 | 11 | 12 | Building 13 | ======== 14 | 15 | That's a standard go project. Make sure you have `libSatHelper` and `libairspy` installed and run: 16 | 17 | ```bash 18 | go get github.com/OpenSatelliteProject/SatHelperApp/cmd/SatHelperApp 19 | ``` 20 | 21 | It will be installed into your `${GOPATH}/bin`. If you have it it on your path, just run `SatHelperApp` 22 | 23 | Have fun! 24 | 25 | 26 | Ubuntu Instructions to get it running 27 | ===================================== 28 | 29 | Base tools: 30 | ```bash 31 | sudo apt install build-essential cmake swig liblimesuite-dev libaec-dev librtlsdr-dev 32 | ``` 33 | 34 | Quick Instructions to get GO 1.10 running: 35 | 36 | ```bash 37 | sudo add-apt-repository ppa:gophers/archive 38 | sudo apt-get update 39 | sudo apt-get install golang-1.10-go 40 | mkdir ~/go 41 | export GOPATH=~/go 42 | export GOROOT=/usr/lib/go-1.10 43 | export PATH=$PATH:$GOPATH/bin:$GOROOT/bin 44 | ``` 45 | 46 | Install LibSatHelper: 47 | ```bash 48 | git clone https://github.com/opensatelliteproject/libsathelper/ 49 | cd libsathelper 50 | make libcorrect 51 | sudo make libcorrect-install 52 | make 53 | sudo make install 54 | ``` 55 | 56 | Install libAirspy: 57 | ```bash 58 | git clone https://github.com/airspy/airspyone_host/ 59 | cd airspyone_host 60 | mkdir build 61 | cd build 62 | cmake .. -DINSTALL_UDEV_RULES=ON 63 | make -j4 64 | sudo make install 65 | sudo ldconfig 66 | ``` 67 | 68 | Quick Instructions to get SatHelperApp running assuming you have Go 1.10, libSatHelper and libAirspy installed. 69 | ```bash 70 | go get github.com/OpenSatelliteProject/SatHelperApp 71 | SatHelperApp 72 | ``` 73 | 74 | This should create a `SatHelperApp.cfg` file in the same folder you ran `SatHelperApp`. You can edit it and tune for your needs. 75 | 76 | Have fun! 77 | 78 | 79 | ## Static libLimeSuite 80 | 81 | LimeSuite by default only compiles dynamic libraries (see https://github.com/myriadrf/LimeSuite/issues/241), so the default behaviour of SatHelperApp wrapper is to dynamic link. However is possible to statically link the libLimeSuite so no external .so / .dll is needed. 82 | 83 | To do that, build the `libLimeSuite` with `-DBUILD_SHARED_LIBS=OFF` to generate `libLimeSuite.a` file. 84 | 85 | ```bash 86 | # Compile Static 87 | cmake .. -DBUILD_SHARED_LIBS=OFF 88 | make -j8 89 | sudo make install 90 | ``` 91 | 92 | Then change [LimeDevice.go](Frontend/LimeDevice/LimeDevice.go) ldflags line from: 93 | 94 | ``` 95 | #cgo LDFLAGS: -lLimeSuite 96 | ``` 97 | 98 | to 99 | 100 | ``` 101 | #cgo LDFLAGS: -l:libLimeSuite.a -l:libstdc++.a -static-libgcc -lm -lusb-1.0 102 | ``` 103 | 104 | And then compile SatHelperApp as normal. 105 | 106 | 107 | Thanks 108 | ====== 109 | 110 | I need to say thanks to all people that helped me with the project: 111 | 112 | - [@hdoverobinson](https://github.com/hdoverobinson) 113 | - [@usa_satcom](https://twitter.com/usa_satcom) 114 | - [@devnulling](https://twitter.com/devnulling/) 115 | 116 | - And many more other people that I can't get the twitter or I don't know how to mention it. Also if forgot about you, let me know to put your name here! 117 | 118 | 119 | And also the people that contributed with: 120 | 121 | - [@hdoverobinson](https://github.com/hdoverobinson) 122 | - [@luigifreitas](https://github.com/luigifreitas) 123 | - [@Gonzih](https://github.com/Gonzih) 124 | 125 | Also thank to these companies for providing hardware for testing OpenSatelliteProject: 126 | 127 | - [LimeMicro](https://limemicro.com/) 128 | - [SDRPlay](https://www.sdrplay.com/) 129 | - [Airspy](https://airspy.com/) 130 | -------------------------------------------------------------------------------- /RPC/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | protoc -I proto/ proto/main.proto --go_out=plugins=grpc:sathelperapp 4 | -------------------------------------------------------------------------------- /RPC/proto/main.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | option java_package = "io.grpc.sathelperapp"; 5 | option java_outer_classname = "SatHelperAppProto"; 6 | 7 | package sathelperapp; 8 | 9 | import "google/protobuf/empty.proto"; 10 | 11 | service Information { 12 | rpc GetStatistics(google.protobuf.Empty) returns (StatData) {} 13 | rpc GetConsoleLines(google.protobuf.Empty) returns (ConsoleData) {} 14 | } 15 | 16 | message ConsoleData { 17 | repeated string consoleLines = 1; 18 | } 19 | 20 | message StatData { 21 | // Signal Quality (in percent) 22 | uint32 signalQuality = 1; 23 | 24 | // If the signal is locked 25 | bool signalLocked = 2; 26 | 27 | // Received Channel Packets 28 | repeated int64 channelPackets = 3 [packed=true]; 29 | 30 | // Reed Solomon Errors 31 | repeated int32 rsErrors = 4 [packed=true]; 32 | 33 | // Sync Word 34 | bytes syncWord = 5; 35 | 36 | // Current SCID 37 | int32 scid = 6; 38 | 39 | // Current VCID 40 | int32 vcid = 7; 41 | 42 | // Decoder Fifo Usage (in percent) 43 | int32 decoderFifoUsage = 8; 44 | 45 | // Demodulator Fifo Usage (in percent) 46 | int32 demodulatorFifoUsage = 9; 47 | 48 | // Viterbi Errors (in bits) 49 | int32 viterbiErrors = 10; 50 | 51 | // Frame Size (in bits) 52 | int32 frameSize = 11; 53 | 54 | // Phase Correction (in degrees) 55 | int32 phaseCorrection = 12; 56 | 57 | // Sync Correlation (in bits) 58 | int32 syncCorrelation = 13; 59 | 60 | // Center Frequency (in Hertz) 61 | uint32 centerFrequency = 14; 62 | 63 | // Demodulator Mode 64 | string mode = 15; 65 | 66 | // Demuxer 67 | string demuxer = 16; 68 | 69 | // Device 70 | string device = 17; 71 | } -------------------------------------------------------------------------------- /RPC/rpcserver.go: -------------------------------------------------------------------------------- 1 | package RPC 2 | 3 | import ( 4 | "fmt" 5 | "github.com/opensatelliteproject/SatHelperApp/Logger" 6 | "github.com/opensatelliteproject/SatHelperApp/RPC/sathelperapp" 7 | "github.com/opensatelliteproject/SatHelperApp/RPC/servers" 8 | "google.golang.org/grpc" 9 | "net" 10 | ) 11 | 12 | type DataSource interface { 13 | servers.InformationFetcher 14 | } 15 | 16 | type Server struct { 17 | infoServer sathelperapp.InformationServer 18 | grpcServer *grpc.Server 19 | } 20 | 21 | func MakeRPCServer(source DataSource) *Server { 22 | return &Server{ 23 | infoServer: servers.MakeInformationServer(source), 24 | } 25 | } 26 | 27 | func (s *Server) Listen(address string) error { 28 | if s.grpcServer != nil { 29 | return fmt.Errorf("server already runing") 30 | } 31 | 32 | lis, err := net.Listen("tcp", address) 33 | if err != nil { 34 | return err 35 | } 36 | s.grpcServer = grpc.NewServer() 37 | sathelperapp.RegisterInformationServer(s.grpcServer, s.infoServer) 38 | go s.serve(lis) 39 | return nil 40 | } 41 | 42 | func (s *Server) serve(conn net.Listener) { 43 | err := s.grpcServer.Serve(conn) 44 | if err != nil { 45 | SLog.Error("RPC Error: %s", err) 46 | } 47 | s.Stop() 48 | } 49 | 50 | func (s *Server) Stop() { 51 | if s.grpcServer == nil { 52 | SLog.Error("RPC Server Already stopped") 53 | return 54 | } 55 | SLog.Info("Stopping RPC Server") 56 | s.grpcServer.Stop() 57 | s.grpcServer = nil 58 | } 59 | -------------------------------------------------------------------------------- /RPC/servers/information.go: -------------------------------------------------------------------------------- 1 | package servers 2 | 3 | import ( 4 | "context" 5 | "github.com/golang/protobuf/ptypes/empty" 6 | "github.com/opensatelliteproject/SatHelperApp/RPC/sathelperapp" 7 | ) 8 | 9 | type InformationFetcher interface { 10 | GetStatistics() (*sathelperapp.StatData, error) 11 | GetConsoleLines() (*sathelperapp.ConsoleData, error) 12 | } 13 | 14 | type informationServer struct { 15 | infoSource InformationFetcher 16 | } 17 | 18 | func MakeInformationServer(source InformationFetcher) sathelperapp.InformationServer { 19 | return &informationServer{ 20 | infoSource: source, 21 | } 22 | } 23 | 24 | func (s *informationServer) GetStatistics(context.Context, *empty.Empty) (*sathelperapp.StatData, error) { 25 | return s.infoSource.GetStatistics() 26 | } 27 | 28 | func (s *informationServer) GetConsoleLines(context.Context, *empty.Empty) (*sathelperapp.ConsoleData, error) { 29 | return s.infoSource.GetConsoleLines() 30 | } 31 | -------------------------------------------------------------------------------- /Tools/tools.go: -------------------------------------------------------------------------------- 1 | package Tools 2 | 3 | import "os" 4 | 5 | func Exists(name string) bool { 6 | if _, err := os.Stat(name); err != nil { 7 | if os.IsNotExist(err) { 8 | return false 9 | } 10 | } 11 | return true 12 | } 13 | 14 | func IsDir(name string) bool { 15 | s, err := os.Stat(name) 16 | if err != nil { 17 | return false 18 | } 19 | 20 | return s.IsDir() 21 | } 22 | 23 | func IsFile(name string) bool { 24 | s, err := os.Stat(name) 25 | if err != nil { 26 | return false 27 | } 28 | 29 | return !s.IsDir() 30 | } 31 | -------------------------------------------------------------------------------- /XRIT/FileParser.go: -------------------------------------------------------------------------------- 1 | package XRIT 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 8 | "github.com/opensatelliteproject/SatHelperApp/XRIT/Structs" 9 | "io" 10 | "os" 11 | ) 12 | 13 | type fpReader interface { 14 | Seek(offset int64, whence int) (int64, error) 15 | Read(p []byte) (n int, err error) 16 | } 17 | 18 | func MemoryParseFile(data []byte) (*Header, error) { 19 | breader := bytes.NewReader(data) 20 | return parseFileFromReader(breader) 21 | } 22 | 23 | func ParseFile(filename string) (*Header, error) { 24 | f, err := os.Open(filename) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | defer f.Close() 30 | 31 | return parseFileFromReader(f) 32 | } 33 | 34 | func parseFileFromReader(reader fpReader) (*Header, error) { 35 | 36 | headerType, _, data, err := readHeader(reader) 37 | 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | if headerType != PacketData.PrimaryHeader { 43 | return nil, fmt.Errorf("first header is not primary (type == %d)", PacketData.PrimaryHeader) 44 | } 45 | 46 | primaryHeader := Structs.MakePrimaryRecord(data) 47 | 48 | pos, _ := reader.Seek(0, io.SeekCurrent) 49 | max := int64(primaryHeader.HeaderLength) 50 | 51 | xh := MakeXRITHeader() 52 | xh.SetHeader(primaryHeader) 53 | 54 | for pos < max { 55 | headerType, _, data, err = readHeader(reader) 56 | if err != nil { 57 | return xh, err 58 | } 59 | 60 | var header Structs.BaseRecord 61 | 62 | switch headerType { 63 | case PacketData.AncillaryTextRecord: 64 | header = Structs.MakeAncillaryText(data) 65 | case PacketData.AnnotationRecord: 66 | header = Structs.MakeAnnotationRecord(data) 67 | case PacketData.DCSFileNameRecord: 68 | header = Structs.MakeDCSFilenameRecord(data) 69 | case PacketData.HeaderStructuredRecord: 70 | header = Structs.MakeHeaderStructuredRecord(data) 71 | case PacketData.ImageDataFunctionRecord: 72 | header = Structs.MakeImageDataFunctionRecord(data) 73 | case PacketData.ImageNavigationRecord: 74 | header = Structs.MakeImageNavigationRecord(data) 75 | case PacketData.ImageStructureRecord: 76 | header = Structs.MakeImageStructureRecord(data) 77 | case PacketData.NOAASpecificHeader: 78 | header = Structs.MakeNOAASpecificRecord(data) 79 | case PacketData.RiceCompressionRecord: 80 | header = Structs.MakeRiceCompressionRecord(data) 81 | case PacketData.SegmentIdentificationRecord: 82 | header = Structs.MakeSegmentIdentificationRecord(data) 83 | case PacketData.TimestampRecord: 84 | header = Structs.MakeTimestampRecord(data) 85 | case PacketData.PrimaryHeader: 86 | header = Structs.MakePrimaryRecord(data) 87 | default: 88 | header = Structs.MakeUnknownHeader(headerType, data) 89 | } 90 | 91 | xh.SetHeader(header) 92 | 93 | pos, _ = reader.Seek(0, io.SeekCurrent) 94 | } 95 | 96 | return xh, nil 97 | } 98 | 99 | // readHeader reads a single header from XRIT File 100 | func readHeader(reader io.Reader) (headerType byte, size uint16, data []byte, err error) { 101 | head := make([]byte, 3) 102 | n, err := reader.Read(head) 103 | 104 | if err != nil { 105 | return 0, 0, nil, err 106 | } 107 | 108 | if n != 3 { 109 | return 0, 0, nil, fmt.Errorf("EOF") 110 | } 111 | 112 | headerType = head[0] 113 | size = binary.BigEndian.Uint16(head[1:]) - 3 // Already read 3 bytes 114 | 115 | data = make([]byte, size) 116 | n, err = reader.Read(data) 117 | 118 | if err != nil { 119 | return 0, 0, nil, err 120 | } 121 | 122 | if n != int(size) { 123 | return 0, 0, nil, fmt.Errorf("EOF") 124 | } 125 | 126 | return headerType, size, data, nil 127 | } 128 | -------------------------------------------------------------------------------- /XRIT/Geo/Tools.go: -------------------------------------------------------------------------------- 1 | // Geo 2 | // Geographic Conversion Tools 3 | // Based on: http://www.cgms-info.org/documents/pdf_cgms_03.pdf 4 | package Geo 5 | 6 | import "math" 7 | 8 | var MAXLON = Deg2Rad(75) 9 | var MINLON = Deg2Rad(-75) 10 | var MAXLAT = Deg2Rad(79) 11 | var MINLAT = Deg2Rad(-79) 12 | 13 | const radiusPoles = 6356.7523 14 | const radiusEquator = 6378.1370 15 | const vehicleDistance = 42142.5833 16 | const planetAspectRatio = (radiusPoles * radiusPoles) / (radiusEquator * radiusEquator) 17 | const surfaceDistance = vehicleDistance*vehicleDistance - radiusEquator*radiusEquator 18 | 19 | const deg2radc = math.Pi / 180 20 | const rad2degc = 180 / math.Pi 21 | 22 | func Deg2Rad(deg float64) float64 { 23 | return deg * deg2radc 24 | } 25 | 26 | func Rad2Deg(rad float64) float64 { 27 | return rad * rad2degc 28 | } 29 | 30 | func LonLat2XY(satLon, lon, lat float64, coff int, cfac float64, loff int, lfac float64) (c, l int) { 31 | cf, lf := LonLat2XYf(satLon, lon, lat, coff, cfac, loff, lfac) 32 | c = int(cf) 33 | l = int(lf) 34 | return 35 | } 36 | 37 | func LonLat2XYf(satLon, lon, lat float64, coff int, cfac float64, loff int, lfac float64) (c, l float64) { 38 | subLon := Deg2Rad(satLon) 39 | lon -= subLon 40 | 41 | lon = math.Min(math.Max(lon, MINLON), MAXLON) 42 | lat = math.Min(math.Max(lat, MINLAT), MAXLAT) 43 | 44 | psi := math.Atan(planetAspectRatio * math.Tan(lat)) 45 | re := radiusPoles / (math.Sqrt(1 - (1-planetAspectRatio)*math.Cos(psi)*math.Cos(psi))) 46 | 47 | r1 := vehicleDistance - re*math.Cos(psi)*math.Cos(lon) 48 | r2 := -1 * re * math.Cos(psi) * math.Sin(lon) 49 | r3 := re * math.Sin(psi) 50 | 51 | rn := math.Sqrt(r1*r1 + r2*r2 + r3*r3) 52 | x := math.Atan(-1 * r2 / r1) 53 | y := math.Asin(-1 * r3 / rn) 54 | x = Rad2Deg(x) 55 | y = Rad2Deg(y) 56 | 57 | c = float64(coff) + (x*cfac)/0x10000 58 | l = float64(loff) + (y*lfac)/0x10000 59 | 60 | return 61 | } 62 | 63 | func XY2LonLat(satLon float64, c, l, coff int, cfac float64, loff int, lfac float64) (lat, lon float64) { 64 | subLon := Deg2Rad(satLon) 65 | 66 | x := float64(c-coff) * 0x10000 / cfac 67 | y := float64(l-loff) * 0x10000 / lfac 68 | 69 | sinx, cosx := math.Sincos(Deg2Rad(x)) 70 | siny, cosy := math.Sincos(Deg2Rad(y)) 71 | 72 | a1 := vehicleDistance * vehicleDistance * cosx * cosx * cosy * cosy 73 | a2 := (cosy*cosy + planetAspectRatio*siny*siny) * surfaceDistance 74 | 75 | if a1 < a2 { 76 | return 0, 0 77 | } 78 | 79 | sd := math.Sqrt(a1 - a2) 80 | sn := (vehicleDistance*cosx*cosy - sd) / (cosy*cosy + planetAspectRatio*siny*siny) 81 | s1 := vehicleDistance - sn*cosx*cosy 82 | s2 := sn * sinx * cosy 83 | s3 := -1 * sn * siny 84 | 85 | sxy := math.Sqrt(s1*s1 + s2*s2) 86 | 87 | lat = 0 88 | lon = 0 89 | 90 | if s1 == 0 { 91 | if s2 > 0 { 92 | lon = Deg2Rad(90) 93 | } else { 94 | lon = Deg2Rad(-90) 95 | } 96 | lon += subLon 97 | } else { 98 | lon = math.Atan(s2/s1) + subLon 99 | } 100 | 101 | if sxy == 0 { 102 | if planetAspectRatio*s3 > 0 { 103 | lat = Deg2Rad(90) 104 | } else { 105 | lat = Deg2Rad(-90) 106 | } 107 | } else { 108 | lat = math.Atan(planetAspectRatio * s3 / sxy) 109 | } 110 | 111 | return 112 | } 113 | -------------------------------------------------------------------------------- /XRIT/NOAAProductID/NOAAProductId.go: -------------------------------------------------------------------------------- 1 | package NOAAProductID 2 | 3 | // NOAAProductID 4 | const ( 5 | NOAA_TEXT = 1 6 | OTHER_SATELLITES_1 = 3 7 | OTHER_SATELLITES_2 = 4 8 | WEATHER_DATA = 6 9 | ABI_RELAY = 7 10 | DCS = 8 11 | HRIT_EMWIN = 9 12 | GOES13_ABI = 13 13 | GOES15_ABI = 15 14 | GOES16_ABI = 16 15 | GOES17_ABI = 17 16 | EMWIN = 42 17 | HIMAWARI8_ABI = 43 18 | ) 19 | -------------------------------------------------------------------------------- /XRIT/PacketData/CompressionType.go: -------------------------------------------------------------------------------- 1 | package PacketData 2 | 3 | import "fmt" 4 | 5 | // CompressionType 6 | const ( 7 | NO_COMPRESSION = 0 8 | LRIT_RICE = 1 9 | JPEG = 2 10 | GIF = 5 11 | ZIP = 10 12 | ) 13 | 14 | var CompressionType = map[int]string{ 15 | NO_COMPRESSION: "No Compression", 16 | LRIT_RICE: "Goloumb Rice (LRIT)", 17 | JPEG: "JPEG", 18 | GIF: "GIF", 19 | ZIP: "ZIP", 20 | } 21 | 22 | var CompressionTypeExtension = map[int]string{ 23 | NO_COMPRESSION: ".bin", 24 | LRIT_RICE: ".bin", 25 | JPEG: ".jpg", 26 | GIF: ".gif", 27 | ZIP: ".zip", 28 | } 29 | 30 | func GetCompressionTypeString(compressionType int) string { 31 | v, ok := CompressionType[compressionType] 32 | if ok { 33 | return v 34 | } 35 | 36 | return fmt.Sprintf("Unknown (%d)", compressionType) 37 | } 38 | 39 | func GetCompressionTypeExtension(compressionType int) string { 40 | v, ok := CompressionTypeExtension[compressionType] 41 | if ok { 42 | return v 43 | } 44 | 45 | return ".data" 46 | } 47 | -------------------------------------------------------------------------------- /XRIT/PacketData/FileTypeCode.go: -------------------------------------------------------------------------------- 1 | package PacketData 2 | 3 | import "fmt" 4 | 5 | // FileTypeCode 6 | const ( 7 | UNKNOWN = -1 8 | 9 | // By LRIT/HRIT Standard 10 | // Section 4 of LRIT/HRIT Global Specification, CGMS 03, August 12, 1999 11 | IMAGE = 0 12 | MESSAGES = 1 13 | TEXT = 2 14 | ENCRYPTION_KEY = 3 15 | RESERVED4 = 4 16 | 17 | METEOROLOGICAL_DATA = 128 18 | 19 | // NOAA 20 | DCS = 130 21 | EMWIN = 214 22 | ) 23 | 24 | var FileTypeCode = map[int]string{ 25 | UNKNOWN: "Unknown", 26 | IMAGE: "Image", 27 | MESSAGES: "Messages", 28 | TEXT: "Text", 29 | ENCRYPTION_KEY: "Encryption Key", 30 | RESERVED4: "Reserved", 31 | METEOROLOGICAL_DATA: "Meteorological Data", 32 | DCS: "DCS", 33 | EMWIN: "EMWIN", 34 | } 35 | 36 | func GetFileTypeCodeString(fileTypeCode int) string { 37 | v, ok := FileTypeCode[fileTypeCode] 38 | if ok { 39 | return v 40 | } 41 | 42 | return fmt.Sprintf("Unknown (%d)", fileTypeCode) 43 | } 44 | -------------------------------------------------------------------------------- /XRIT/PacketData/HeaderType.go: -------------------------------------------------------------------------------- 1 | package PacketData 2 | 3 | import "fmt" 4 | 5 | // HeaderType 6 | const ( 7 | Unknown = -1 8 | PrimaryHeader = 0 9 | ImageStructureRecord = 1 10 | ImageNavigationRecord = 2 11 | ImageDataFunctionRecord = 3 12 | AnnotationRecord = 4 13 | TimestampRecord = 5 14 | AncillaryTextRecord = 6 15 | KeyRecord = 7 16 | Head9 = 9 // Weird 17 | 18 | SegmentIdentificationRecord = 128 19 | NOAASpecificHeader = 129 20 | HeaderStructuredRecord = 130 21 | RiceCompressionRecord = 131 22 | DCSFileNameRecord = 132 23 | ) 24 | 25 | var HeaderType = map[int]string{ 26 | Unknown: "Unknown", 27 | PrimaryHeader: "Primary Header", 28 | ImageStructureRecord: "Image Structure Record", 29 | ImageNavigationRecord: "Image Navigation Record", 30 | ImageDataFunctionRecord: "Image Data Function Record", 31 | AnnotationRecord: "Annotation Record", 32 | TimestampRecord: "Timestamp Record", 33 | AncillaryTextRecord: "Ancillary Text Record", 34 | KeyRecord: "Key Record", 35 | Head9: "Unknown", 36 | SegmentIdentificationRecord: "Segment Identification Record", 37 | NOAASpecificHeader: "NOAA Specific Header", 38 | HeaderStructuredRecord: "Header Structured Record", 39 | RiceCompressionRecord: "Rice Compression Record", 40 | DCSFileNameRecord: "DCS StringData Record", 41 | } 42 | 43 | func GetHeaderTypeString(headerType int) string { 44 | v, ok := HeaderType[headerType] 45 | if ok { 46 | return v 47 | } 48 | 49 | return fmt.Sprintf("Unknown (%d)", headerType) 50 | } 51 | -------------------------------------------------------------------------------- /XRIT/PacketData/NOAAProduct.go: -------------------------------------------------------------------------------- 1 | package PacketData 2 | 3 | type NOAAProduct struct { 4 | ID int 5 | Name string 6 | SubProducts map[int]NOAASubProduct 7 | } 8 | 9 | func MakeNOAAProduct(id int) NOAAProduct { 10 | return MakeNOAAProductWithName(id, "") 11 | } 12 | 13 | func MakeNOAAProductWithName(id int, name string) NOAAProduct { 14 | return MakeNOAAProductWithSubProductsAndName(id, name, map[int]NOAASubProduct{}) 15 | } 16 | 17 | func MakeNOAAProductWithSubProductsAndName(id int, name string, subProducts map[int]NOAASubProduct) NOAAProduct { 18 | return NOAAProduct{ 19 | ID: id, 20 | Name: name, 21 | SubProducts: subProducts, 22 | } 23 | } 24 | 25 | func (np *NOAAProduct) GetSubProduct(id int) NOAASubProduct { 26 | val, ok := np.SubProducts[id] 27 | 28 | if !ok { 29 | return MakeSubProduct(id, "Unknown") 30 | } 31 | 32 | return val 33 | } 34 | -------------------------------------------------------------------------------- /XRIT/PacketData/NOAASubProduct.go: -------------------------------------------------------------------------------- 1 | package PacketData 2 | 3 | type NOAASubProduct struct { 4 | ID int 5 | Name string 6 | } 7 | 8 | func MakeSubProduct(id int, name string) NOAASubProduct { 9 | return NOAASubProduct{ 10 | ID: id, 11 | Name: name, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XRIT/ScannerSubProduct/SubProductID.go: -------------------------------------------------------------------------------- 1 | package ScannerSubProduct 2 | 3 | const ( 4 | NONE = 0 5 | 6 | // Infrared Images 7 | INFRARED_FULLDISK = 1 8 | INFRARED_NORTHERN = 2 9 | INFRARED_SOUTHERN = 3 10 | INFRARED_UNITEDSTATES = 4 11 | INFRARED_AREA_OF_INTEREST = 5 12 | 13 | // Visible Images 14 | VISIBLE_FULLDISK = 11 15 | VISIBLE_NORTHERN = 12 16 | VISIBLE_SOUTHERN = 13 17 | VISIBLE_UNITEDSTATES = 14 18 | VISIBLE_AREA_OF_INTEREST = 15 19 | 20 | // Water Vapour 21 | WATERVAPOUR_FULLDISK = 21 22 | WATERVAPOUR_NORTHERN = 22 23 | WATERVAPOUR_SOUTHERN = 23 24 | WATERVAPOUR_UNITEDSTATES = 24 25 | WATERVAPOUR_AREA_OF_INTEREST = 25 26 | ) 27 | -------------------------------------------------------------------------------- /XRIT/Structs/BaseRecord.go: -------------------------------------------------------------------------------- 1 | package Structs 2 | 3 | type BaseRecord interface { 4 | GetType() int 5 | } 6 | -------------------------------------------------------------------------------- /XRIT/Structs/DCSPacket.go: -------------------------------------------------------------------------------- 1 | package Structs 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | ) 7 | 8 | const DCSFrameMark = "\x02\x02\x18" 9 | 10 | type DCSPacket struct { 11 | Address string 12 | DateTime time.Time 13 | Status string 14 | Signal int 15 | FrequencyOffset int 16 | ModIndexNormal string 17 | DataQualNominal string 18 | Channel string 19 | SourceCode string 20 | } 21 | 22 | func DCSDateToTime(date string) time.Time { 23 | // %y%j%H%M%S 24 | year := "20" + date[:2] 25 | dayOfYear, _ := strconv.ParseInt(date[2:5], 10, 32) 26 | hour := date[5:7] 27 | minute := date[7:9] 28 | second := date[9:11] 29 | dt, _ := time.Parse("2006150405", year+hour+minute+second) 30 | return dt.Add(time.Duration(dayOfYear*3600*24) * time.Second) 31 | } 32 | 33 | func MakeDCSPacket(data []byte) *DCSPacket { 34 | signal, _ := strconv.ParseInt(string(data[21:23]), 10, 32) 35 | freqOffset, _ := strconv.ParseInt(string(data[23:25]), 10, 32) 36 | 37 | return &DCSPacket{ 38 | Address: string(data[:9]), 39 | DateTime: DCSDateToTime(string(data[9:20])), 40 | Status: string(data[20]), 41 | Signal: int(signal), 42 | FrequencyOffset: int(freqOffset), 43 | ModIndexNormal: string(data[25]), 44 | DataQualNominal: string(data[26]), 45 | Channel: string(data[27:31]), 46 | SourceCode: string(data[31:33]), 47 | } 48 | } 49 | 50 | type DCSData struct { 51 | Header string 52 | Packets []*DCSPacket 53 | } 54 | 55 | func ParseDCS(data []byte) *DCSData { 56 | baseHeader := string(data[:64]) 57 | content := data[64:] 58 | 59 | packets := make([]*DCSPacket, 0) 60 | lastStart := 0 61 | 62 | for i := 0; i < len(content)-len(DCSFrameMark); i++ { 63 | if string(content[i:i+3]) == DCSFrameMark { 64 | if i-1 > 0 { 65 | packetData := content[lastStart : i-1] 66 | packets = append(packets, MakeDCSPacket(packetData)) 67 | } 68 | i += 3 69 | lastStart = i 70 | } 71 | } 72 | 73 | return &DCSData{ 74 | Header: baseHeader, 75 | Packets: packets, 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /XRIT/Structs/ImageNavigationRecord.go: -------------------------------------------------------------------------------- 1 | package Structs 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 6 | "strings" 7 | ) 8 | 9 | type ImageNavigationRecord struct { 10 | Type byte 11 | 12 | ProjectionName string 13 | ColumnScalingFactor uint32 14 | LineScalingFactor uint32 15 | ColumnOffset int32 16 | LineOffset int32 17 | } 18 | 19 | func MakeImageNavigationRecord(data []byte) *ImageNavigationRecord { 20 | inr := ImageNavigationRecord{} 21 | 22 | inr.Type = PacketData.ImageNavigationRecord 23 | 24 | inr.ProjectionName = strings.Trim(string(data[:32]), " \n\r\x00") 25 | inr.ColumnScalingFactor = binary.BigEndian.Uint32(data[32:36]) 26 | inr.LineScalingFactor = binary.BigEndian.Uint32(data[36:40]) 27 | inr.ColumnOffset = int32(binary.BigEndian.Uint32(data[40:44])) 28 | inr.LineOffset = int32(binary.BigEndian.Uint32(data[44:48])) 29 | 30 | return &inr 31 | } 32 | 33 | func (imr *ImageNavigationRecord) GetType() int { 34 | return int(imr.Type) 35 | } 36 | -------------------------------------------------------------------------------- /XRIT/Structs/ImageStructureRecord.go: -------------------------------------------------------------------------------- 1 | package Structs 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 6 | ) 7 | 8 | type ImageStructureRecord struct { 9 | Type byte 10 | 11 | BitsPerPixel byte 12 | Columns uint16 13 | Lines uint16 14 | Compression byte 15 | } 16 | 17 | func MakeImageStructureRecord(data []byte) *ImageStructureRecord { 18 | v := ImageStructureRecord{} 19 | 20 | v.Type = PacketData.ImageStructureRecord 21 | 22 | v.BitsPerPixel = data[0] 23 | v.Columns = binary.BigEndian.Uint16(data[1:3]) 24 | v.Lines = binary.BigEndian.Uint16(data[3:5]) 25 | v.Compression = data[5] 26 | 27 | return &v 28 | } 29 | 30 | func (isr *ImageStructureRecord) GetType() int { 31 | return int(isr.Type) 32 | } 33 | -------------------------------------------------------------------------------- /XRIT/Structs/NOAASpecificRecord.go: -------------------------------------------------------------------------------- 1 | package Structs 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 6 | "github.com/opensatelliteproject/SatHelperApp/XRIT/Presets" 7 | ) 8 | 9 | type NOAASpecificRecord struct { 10 | Type byte 11 | Signature string 12 | ProductID uint16 13 | ProductSubID uint16 14 | Parameter uint16 15 | Compression byte 16 | } 17 | 18 | func MakeNOAASpecificRecord(data []byte) *NOAASpecificRecord { 19 | v := NOAASpecificRecord{} 20 | 21 | v.Type = PacketData.NOAASpecificHeader 22 | 23 | v.Signature = string(data[:4]) 24 | v.ProductID = binary.BigEndian.Uint16(data[4:6]) 25 | v.ProductSubID = binary.BigEndian.Uint16(data[6:8]) 26 | v.Parameter = binary.BigEndian.Uint16(data[8:10]) 27 | v.Compression = data[10] 28 | 29 | return &v 30 | } 31 | 32 | func (nsr *NOAASpecificRecord) Product() PacketData.NOAAProduct { 33 | return Presets.GetProductById(int(nsr.ProductID)) 34 | } 35 | 36 | func (nsr *NOAASpecificRecord) SubProduct() PacketData.NOAASubProduct { 37 | v := nsr.Product() 38 | return v.GetSubProduct(int(nsr.ProductSubID)) 39 | } 40 | 41 | func (nsr *NOAASpecificRecord) GetType() int { 42 | return int(nsr.Type) 43 | } 44 | -------------------------------------------------------------------------------- /XRIT/Structs/PrimaryRecord.go: -------------------------------------------------------------------------------- 1 | package Structs 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 6 | ) 7 | 8 | type PrimaryRecord struct { 9 | Type byte 10 | Size uint16 11 | FileTypeCode byte 12 | HeaderLength uint32 13 | DataLength uint64 14 | } 15 | 16 | func MakePrimaryRecord(data []byte) *PrimaryRecord { 17 | v := PrimaryRecord{} 18 | 19 | v.Type = PacketData.PrimaryHeader 20 | 21 | v.FileTypeCode = data[0] 22 | v.HeaderLength = binary.BigEndian.Uint32(data[1:5]) 23 | v.DataLength = binary.BigEndian.Uint64(data[5:13]) 24 | 25 | return &v 26 | } 27 | 28 | func (pr *PrimaryRecord) GetType() int { 29 | return int(pr.Type) 30 | } 31 | -------------------------------------------------------------------------------- /XRIT/Structs/RiceCompressionRecord.go: -------------------------------------------------------------------------------- 1 | package Structs 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 6 | ) 7 | 8 | type RiceCompressionRecord struct { 9 | Type byte 10 | Flags uint16 11 | Pixel byte 12 | Line byte 13 | } 14 | 15 | func MakeRiceCompressionRecord(data []byte) *RiceCompressionRecord { 16 | v := RiceCompressionRecord{} 17 | 18 | v.Type = PacketData.RiceCompressionRecord 19 | 20 | v.Flags = binary.BigEndian.Uint16(data[0:2]) 21 | v.Pixel = data[2] 22 | v.Line = data[3] 23 | 24 | return &v 25 | } 26 | 27 | func (rcr *RiceCompressionRecord) GetType() int { 28 | return int(rcr.Type) 29 | } 30 | -------------------------------------------------------------------------------- /XRIT/Structs/SegmentIdentificationRecord.go: -------------------------------------------------------------------------------- 1 | package Structs 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 6 | ) 7 | 8 | type SegmentIdentificationRecord struct { 9 | Type byte 10 | ImageID uint16 11 | Sequence uint16 12 | StartColumn uint16 13 | StartLine uint16 14 | MaxSegments uint16 15 | MaxColumns uint16 16 | MaxRows uint16 17 | COMS1 bool 18 | } 19 | 20 | func MakeSegmentIdentificationRecord(data []byte) *SegmentIdentificationRecord { 21 | v := SegmentIdentificationRecord{} 22 | 23 | v.Type = PacketData.SegmentIdentificationRecord 24 | 25 | if len(data) == 4 { 26 | v.StartColumn = 0 27 | v.Sequence = uint16(data[0]) 28 | v.MaxSegments = uint16(data[1]) 29 | v.StartLine = binary.BigEndian.Uint16(data[2:4]) 30 | v.COMS1 = true 31 | } else { 32 | v.ImageID = binary.BigEndian.Uint16(data[0:2]) 33 | v.Sequence = binary.BigEndian.Uint16(data[2:4]) 34 | v.StartColumn = binary.BigEndian.Uint16(data[4:6]) 35 | v.StartLine = binary.BigEndian.Uint16(data[6:8]) 36 | v.MaxSegments = binary.BigEndian.Uint16(data[8:10]) 37 | v.MaxColumns = binary.BigEndian.Uint16(data[10:12]) 38 | v.MaxRows = binary.BigEndian.Uint16(data[12:14]) 39 | v.COMS1 = false 40 | } 41 | 42 | return &v 43 | } 44 | 45 | func (sir *SegmentIdentificationRecord) GetType() int { 46 | return int(sir.Type) 47 | } 48 | -------------------------------------------------------------------------------- /XRIT/Structs/StringFieldRecord.go: -------------------------------------------------------------------------------- 1 | package Structs 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 5 | "strings" 6 | ) 7 | 8 | type StringFieldRecord struct { 9 | Type byte 10 | StringData string 11 | } 12 | 13 | type AncillaryText StringFieldRecord 14 | type AnnotationRecord StringFieldRecord 15 | type DCSFilenameRecord StringFieldRecord 16 | type HeaderStructuredRecord StringFieldRecord 17 | type ImageDataFunctionRecord StringFieldRecord 18 | 19 | func MakeAncillaryText(data []byte) *AncillaryText { 20 | v := AncillaryText{} 21 | 22 | v.Type = PacketData.AncillaryTextRecord 23 | 24 | v.StringData = strings.Trim(string(data), " \n\r") 25 | 26 | return &v 27 | } 28 | func MakeAnnotationRecord(data []byte) *AnnotationRecord { 29 | v := AnnotationRecord{} 30 | 31 | v.Type = PacketData.AnnotationRecord 32 | 33 | v.StringData = strings.Trim(string(data), " \n\r") 34 | 35 | return &v 36 | } 37 | func MakeDCSFilenameRecord(data []byte) *DCSFilenameRecord { 38 | v := DCSFilenameRecord{} 39 | 40 | v.Type = PacketData.DCSFileNameRecord 41 | 42 | v.StringData = strings.Trim(string(data), " \n\r") 43 | 44 | return &v 45 | } 46 | func MakeHeaderStructuredRecord(data []byte) *HeaderStructuredRecord { 47 | v := HeaderStructuredRecord{} 48 | 49 | v.Type = PacketData.HeaderStructuredRecord 50 | 51 | v.StringData = strings.Trim(string(data), " \n\r") 52 | 53 | return &v 54 | } 55 | func MakeImageDataFunctionRecord(data []byte) *ImageDataFunctionRecord { 56 | v := ImageDataFunctionRecord{} 57 | 58 | v.Type = PacketData.ImageDataFunctionRecord 59 | 60 | v.StringData = strings.Trim(string(data), " \n\r") 61 | 62 | return &v 63 | } 64 | 65 | func (sfr *StringFieldRecord) GetType() int { 66 | return int(sfr.Type) 67 | } 68 | func (a *AncillaryText) GetType() int { 69 | return int(a.Type) 70 | } 71 | func (a *AnnotationRecord) GetType() int { 72 | return int(a.Type) 73 | } 74 | func (a *DCSFilenameRecord) GetType() int { 75 | return int(a.Type) 76 | } 77 | func (a *HeaderStructuredRecord) GetType() int { 78 | return int(a.Type) 79 | } 80 | func (a *ImageDataFunctionRecord) GetType() int { 81 | return int(a.Type) 82 | } 83 | -------------------------------------------------------------------------------- /XRIT/Structs/TimestampRecord.go: -------------------------------------------------------------------------------- 1 | package Structs 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 6 | "time" 7 | ) 8 | 9 | type TimestampRecord struct { 10 | Type byte 11 | Days uint16 12 | Milisseconds uint32 13 | } 14 | 15 | func MakeTimestampRecord(data []byte) *TimestampRecord { 16 | v := TimestampRecord{} 17 | 18 | v.Type = PacketData.TimestampRecord 19 | v.Days = binary.BigEndian.Uint16(data[1:3]) 20 | v.Milisseconds = binary.BigEndian.Uint32(data[3:7]) 21 | 22 | return &v 23 | } 24 | 25 | func (tr *TimestampRecord) GetDateTime() time.Time { 26 | d := time.Date(1958, 1, 1, 0, 0, 0, 0, time.UTC) 27 | d = d.Add(time.Duration(int64(tr.Days)*3600*24) * time.Second) 28 | d = d.Add(time.Duration(tr.Milisseconds) * time.Millisecond) 29 | 30 | return d 31 | } 32 | 33 | func (tr *TimestampRecord) GetType() int { 34 | return int(tr.Type) 35 | } 36 | -------------------------------------------------------------------------------- /XRIT/Structs/UnknownHeader.go: -------------------------------------------------------------------------------- 1 | package Structs 2 | 3 | type UnknownHeader struct { 4 | Type byte 5 | Data []byte 6 | } 7 | 8 | func MakeUnknownHeader(headerType byte, data []byte) *UnknownHeader { 9 | v := UnknownHeader{} 10 | 11 | v.Type = headerType 12 | 13 | v.Data = data 14 | 15 | return &v 16 | } 17 | 18 | func (uh *UnknownHeader) GetType() int { 19 | return int(uh.Type) 20 | } 21 | -------------------------------------------------------------------------------- /XRIT/VCID2Name.go: -------------------------------------------------------------------------------- 1 | package XRIT 2 | 3 | // VCID2Name are GOES 16/17/18 VCIDs names 4 | var VCID2Name = map[int]string{ 5 | 0: "Admin Text", 6 | 1: "Mesoscale", 7 | 2: "GOES-ABI", // Band 2 8 | 6: "GOES15", 9 | 7: "GOES-ABI", // Band 7 10 | 8: "GOES-ABI", // Band 8 11 | 9: "GOES-ABI", // Band 8 12 | 13: "GOES-ABI", // Band 13 13 | 14: "GOES-ABI", // Band 14 14 | 15: "GOES-ABI", // Band 15 15 | 17: "GOES17", 16 | 20: "EMWIN", 17 | 21: "EMWIN", 18 | 22: "EMWIN", 19 | 23: "NWS", 20 | 24: "NHC", 21 | 25: "GOES16-JPG", 22 | 26: "INTL", 23 | 30: "DCS", 24 | 31: "DCS", 25 | 32: "DCS", 26 | 60: "Himawari", 27 | 63: "IDLE", 28 | } 29 | 30 | /* 31 | 0 Imagery Admin Text Messages 32 | 1 Imagery Mesoscale (ch. 2, 7, 13) 33 | 2 Imagery Band 2 - Red 34 | 6 Imagery GOES-15 35 | 7 Imagery Band 7 - Shortwave Window 36 | 8 Imagery Band 8 37 | 9 Imagery Band 9 - Mid-Level Trop 38 | 13 Imagery Band 13 39 | 14 Imagery Band 14 - IR 40 | 15 Imagery Band 15 41 | 20 EMWIN Priority 42 | 21 EMWIN Graphics 43 | 22 EMWIN Other 44 | 23 Imagery NWS Products 45 | 24 Imagery NHC Graphics Products 46 | 25 Imagery GOES-R JPG Products 47 | 26 Imagery International Graphics Products 48 | 30 DCS DCS Admin 49 | 31 DCS DCS Data 50 | 60 Imagery Himawari 51 | 52 | */ 53 | -------------------------------------------------------------------------------- /ccsds/FileHandlers.go: -------------------------------------------------------------------------------- 1 | package ccsds 2 | 3 | import ( 4 | "archive/zip" 5 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor" 6 | "github.com/opensatelliteproject/SatHelperApp/Logger" 7 | "github.com/opensatelliteproject/SatHelperApp/XRIT" 8 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 9 | "github.com/opensatelliteproject/SatHelperApp/metrics" 10 | "io" 11 | "io/ioutil" 12 | "os" 13 | "path" 14 | "strings" 15 | ) 16 | 17 | func PostHandleFile(filename, outBase string, vcid int, ip *ImageProcessor.ImageProcessor) { 18 | metrics.NewFile() 19 | xh, err := XRIT.ParseFile(filename) 20 | 21 | if err != nil { 22 | SLog.Error("PostHandleFile - Error parsing file %s: %s", filename, err) 23 | _ = os.Remove(filename) 24 | return 25 | } 26 | 27 | switch xh.Compression() { 28 | case PacketData.ZIP: 29 | SLog.Debug("File %s is a zip. Decompressing it.", filename) 30 | stripFileHeader(filename, int64(xh.PrimaryHeader.HeaderLength)) 31 | handleZipFile(filename, outBase) 32 | return 33 | case PacketData.GIF: 34 | newName := strings.Replace(filename, ".lrit", PacketData.GetCompressionTypeExtension(xh.Compression()), 1) 35 | handleRawFileStrip(filename, newName, xh) 36 | return 37 | case PacketData.JPEG: 38 | newName := strings.Replace(filename, ".lrit", PacketData.GetCompressionTypeExtension(xh.Compression()), 1) 39 | handleRawFileStrip(filename, newName, xh) 40 | return 41 | } 42 | 43 | switch xh.PrimaryHeader.FileTypeCode { 44 | case PacketData.TEXT: 45 | newName := strings.Replace(filename, ".lrit", ".txt", 1) 46 | handleRawFileStrip(filename, newName, xh) 47 | case PacketData.IMAGE: 48 | ip.ProcessImage(filename) 49 | } 50 | } 51 | 52 | func handleRawFileStrip(filename string, newName string, xh *XRIT.Header) { 53 | SLog.Debug("File %s is a %s.", filename, PacketData.GetCompressionTypeString(xh.Compression())) 54 | stripFileHeader(filename, int64(xh.PrimaryHeader.HeaderLength)) 55 | err := os.Rename(filename, newName) 56 | if err != nil { 57 | SLog.Error("Error moving file %s to %s: %s", filename, newName, err) 58 | } 59 | } 60 | 61 | func handleZipFile(filename, outBase string) { 62 | r, err := zip.OpenReader(filename) 63 | if err != nil { 64 | SLog.Error("Error opening %s as zip file: %s", filename, err) 65 | return 66 | } 67 | 68 | for _, zipFile := range r.File { 69 | SLog.Info("New file decompressed: %s", zipFile.Name) 70 | outFile := path.Join(outBase, zipFile.Name) 71 | f, err := os.Create(outFile) 72 | if err != nil { 73 | SLog.Error("Error creating %s: %s", outFile, err) 74 | continue 75 | } 76 | 77 | r, err := zipFile.Open() 78 | 79 | if err != nil { 80 | SLog.Error("Internal ZIP Error: %s", err) 81 | _ = f.Close() 82 | continue 83 | } 84 | 85 | _, err = io.Copy(f, r) 86 | 87 | if err != nil { 88 | SLog.Error("Error writing data: %s", err) 89 | } 90 | 91 | _ = f.Close() 92 | _ = r.Close() 93 | } 94 | 95 | _ = r.Close() 96 | SLog.Debug("Removing file %s", filename) 97 | _ = os.Remove(filename) 98 | } 99 | 100 | func stripFileHeader(filename string, offset int64) { 101 | data, err := ioutil.ReadFile(filename) 102 | 103 | if err != nil { 104 | SLog.Error("Error reading file %s: %s", filename, err) 105 | return 106 | } 107 | 108 | if len(data) < int(offset) { 109 | SLog.Error("File %s smaller than offset.", filename) 110 | return 111 | } 112 | 113 | data = data[offset:] 114 | 115 | err = ioutil.WriteFile(filename, data, os.ModePerm) 116 | 117 | if err != nil { 118 | SLog.Error("Error writing file %s: %s", filename, err) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /ccsds/MSDU.go: -------------------------------------------------------------------------------- 1 | package ccsds 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/opensatelliteproject/SatHelperApp/Logger" 6 | ) 7 | 8 | type MSDU struct { 9 | ChannelId int 10 | Version int 11 | APID int 12 | Priority int 13 | Type int 14 | HasSecondHeader bool 15 | PrimaryHeader []byte 16 | Sequence int 17 | PacketNumber int 18 | PacketLength int 19 | FullPacketLength int 20 | Data []byte 21 | 22 | CRC uint16 23 | CalculatedCRC uint16 24 | 25 | lengthValid bool 26 | fullData []byte 27 | currentFullDataPos int 28 | headerParsed bool 29 | closed bool 30 | } 31 | 32 | func MakeMSDUWithHeader(channelId int, header []byte) *MSDU { 33 | m := MSDU{ 34 | ChannelId: channelId, 35 | PrimaryHeader: make([]byte, 6), 36 | fullData: make([]byte, 0, 8192+6), // Max Length is 8192 + 6 bytes header 37 | headerParsed: false, 38 | } 39 | m.fullData = m.fullData[:len(header)] 40 | copy(m.fullData, header) 41 | m.currentFullDataPos += len(header) 42 | m.parseHeader() 43 | 44 | return &m 45 | } 46 | 47 | func (msdu *MSDU) parseHeader() { 48 | if len(msdu.fullData) < 6 || msdu.headerParsed { 49 | return 50 | } 51 | 52 | data := msdu.fullData 53 | 54 | o := binary.BigEndian.Uint16(data[:2]) 55 | 56 | msdu.Version = int((o & 0xE000) >> 13) 57 | 58 | msdu.Type = int((o & 0x1000) >> 12) 59 | msdu.HasSecondHeader = ((o & 0x800) >> 11) > 0 60 | msdu.APID = int(o & 0x7FF) 61 | msdu.Priority = msdu.APID / 32 62 | 63 | o = binary.BigEndian.Uint16(data[2:4]) 64 | 65 | msdu.Sequence = int((o & 0xC000) >> 14) 66 | msdu.PacketNumber = int(o & 0x3FFF) 67 | msdu.PacketLength = SizeFromMSDUHeader(data) + 1 68 | msdu.FullPacketLength = msdu.PacketLength + 6 69 | 70 | if msdu.FullPacketLength > cap(msdu.fullData) { 71 | SLog.Warn("Received a packet that is reporting to be bigger than %d (got %d). Skipping parse...", cap(msdu.fullData), msdu.FullPacketLength) 72 | msdu.FullPacketLength = 0 73 | msdu.finalize() 74 | return 75 | } 76 | 77 | msdu.fullData = msdu.fullData[:msdu.FullPacketLength] // Already 8192 reserved, so we can do that. 78 | msdu.headerParsed = true 79 | } 80 | 81 | func (msdu *MSDU) AddBytes(data []byte) []byte { 82 | if msdu.headerParsed { 83 | bytesToAdd := msdu.FullPacketLength - msdu.currentFullDataPos 84 | if len(data) < bytesToAdd { 85 | bytesToAdd = len(data) 86 | } 87 | 88 | a := data[:bytesToAdd] 89 | data = data[bytesToAdd:] 90 | 91 | copy(msdu.fullData[msdu.currentFullDataPos:], a) 92 | 93 | msdu.currentFullDataPos += bytesToAdd 94 | 95 | if msdu.currentFullDataPos == msdu.FullPacketLength { 96 | msdu.finalize() 97 | } 98 | 99 | return data 100 | } 101 | 102 | remainingBytes := len(msdu.fullData) - msdu.currentFullDataPos 103 | if remainingBytes < len(data) { 104 | missingBytes := len(data) - remainingBytes 105 | msdu.fullData = msdu.fullData[:len(msdu.fullData)+missingBytes] // Already 8192 reserved, so we can do that. 106 | } 107 | 108 | copy(msdu.fullData[msdu.currentFullDataPos:], data) 109 | msdu.currentFullDataPos += len(data) 110 | 111 | if msdu.currentFullDataPos > 6 { 112 | msdu.parseHeader() 113 | } 114 | 115 | return data[0:0] 116 | } 117 | 118 | func (msdu *MSDU) finalize() { 119 | if !msdu.closed { 120 | data := msdu.fullData 121 | data = data[6:] 122 | 123 | msdu.lengthValid = msdu.PacketLength == len(data) 124 | if len(data) > 2 { 125 | msdu.CRC = binary.BigEndian.Uint16(data[len(data)-2:]) 126 | msdu.CalculatedCRC = CRC(data[:len(data)-2]) 127 | data = data[:len(data)-2] 128 | } 129 | 130 | msdu.Data = data 131 | msdu.closed = true 132 | } 133 | } 134 | 135 | func (msdu *MSDU) Closed() bool { 136 | return msdu.closed 137 | } 138 | 139 | func (msdu *MSDU) Clone() *MSDU { 140 | v := *msdu 141 | return &v 142 | } 143 | 144 | func (msdu *MSDU) Valid() bool { 145 | if !msdu.lengthValid { 146 | SLog.Debug("Not valid because length. Expected %d got %d", msdu.PacketLength, len(msdu.Data)) 147 | return false 148 | } 149 | 150 | if msdu.APID == 2047 { 151 | return true // Don't check CRC for Fill Frames 152 | } 153 | 154 | if msdu.CRC != msdu.CalculatedCRC { 155 | SLog.Debug("Not valid because CRC. Expected 0x%04x got 0x%04x", msdu.CRC, msdu.CalculatedCRC) 156 | return false 157 | } 158 | 159 | return true 160 | } 161 | 162 | func SizeFromMSDUHeader(data []byte) int { 163 | if len(data) < 6 { 164 | panic("Not enough data for sizing MSDU!!") 165 | } 166 | 167 | l := binary.BigEndian.Uint16(data[4:6]) 168 | return int(l) 169 | } 170 | 171 | func CRC(data []byte) uint16 { 172 | lsb := byte(0xFF) 173 | msb := byte(0xFF) 174 | 175 | for _, v := range data { 176 | x := v ^ msb 177 | x ^= x >> 4 178 | msb = lsb ^ (x >> 3) ^ (x << 4) 179 | lsb = x ^ (x << 5) 180 | } 181 | 182 | return uint16(msb)<<8 + uint16(lsb) 183 | } 184 | -------------------------------------------------------------------------------- /ccsds/MSDUInfo.go: -------------------------------------------------------------------------------- 1 | package ccsds 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/XRIT" 5 | "time" 6 | ) 7 | 8 | const MSDUTimeout = 15 * 60 // 15 minutes 9 | 10 | type MSDUInfo struct { 11 | APID int 12 | ReceivedTime time.Time 13 | FileName string 14 | LastPacketNumber int 15 | Header *XRIT.Header 16 | } 17 | 18 | func MakeMSDUInfo() *MSDUInfo { 19 | return &MSDUInfo{ 20 | ReceivedTime: time.Now(), 21 | } 22 | } 23 | 24 | func (mi *MSDUInfo) Expired() bool { 25 | return time.Since(mi.ReceivedTime).Seconds() > MSDUTimeout 26 | } 27 | 28 | func (mi *MSDUInfo) Refresh() { 29 | mi.ReceivedTime = time.Now() 30 | } 31 | -------------------------------------------------------------------------------- /ccsds/SequenceType.go: -------------------------------------------------------------------------------- 1 | package ccsds 2 | 3 | const ( 4 | SequenceContinuedSegment = 0 5 | SequenceFirstSegment = 1 6 | SequenceLastSegment = 2 7 | SequenceSingleData = 3 8 | ) 9 | -------------------------------------------------------------------------------- /ccsds/VCDU.go: -------------------------------------------------------------------------------- 1 | package ccsds 2 | 3 | type VCDU struct { 4 | data []byte 5 | } 6 | 7 | func MakeVCDU(data []byte) *VCDU { 8 | return &VCDU{ 9 | data: data, 10 | } 11 | } 12 | 13 | func (vcdu *VCDU) Version() int { 14 | return int(vcdu.data[0]&0xC0) >> 6 15 | } 16 | 17 | func (vcdu *VCDU) SCID() int { 18 | return int(vcdu.data[0]&0x3f)<<2 | int(vcdu.data[1]&0xc0)>>6 19 | } 20 | 21 | func (vcdu *VCDU) VCID() int { 22 | return int(vcdu.data[1]) & 0x3f 23 | } 24 | 25 | func (vcdu *VCDU) Counter() int { 26 | return (int(vcdu.data[2]) << 16) | (int(vcdu.data[3]) << 8) | int(vcdu.data[4]) 27 | } 28 | 29 | func (vcdu *VCDU) Data() []byte { 30 | return vcdu.data[6:] 31 | } 32 | 33 | func (vcdu *VCDU) Replay() bool { 34 | return (vcdu.data[5] & 0x80) > 0 35 | } 36 | -------------------------------------------------------------------------------- /ccsds/parser.go: -------------------------------------------------------------------------------- 1 | package ccsds 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/metrics" 5 | "sync" 6 | ) 7 | 8 | type Demuxer struct { 9 | sync.Mutex 10 | frameSize int 11 | frameBuffer []byte 12 | 13 | transports map[int]*TransportParser 14 | lastFrame map[int]int 15 | framesLost map[int]uint64 16 | 17 | skipVcids []int 18 | 19 | cbNewVCID func(int) 20 | cbOnFrameLost func(channelId, currentFrame, lastFrame int) 21 | 22 | fileAssembler *FileAssembler 23 | } 24 | 25 | func MakeDemuxer() *Demuxer { 26 | return &Demuxer{ 27 | frameSize: 892, 28 | lastFrame: make(map[int]int), 29 | framesLost: make(map[int]uint64), 30 | frameBuffer: make([]byte, 0), 31 | transports: make(map[int]*TransportParser), 32 | fileAssembler: MakeFileAssembler(), 33 | skipVcids: make([]int, 0), 34 | } 35 | } 36 | 37 | func (dm *Demuxer) AddSkipVCID(vcid int) { 38 | dm.skipVcids = append(dm.skipVcids, vcid) 39 | } 40 | 41 | func (dm *Demuxer) shouldSkip(vcid int) bool { 42 | for _, v := range dm.skipVcids { 43 | if v == vcid { 44 | return true 45 | } 46 | } 47 | 48 | return false 49 | } 50 | 51 | func (dm *Demuxer) SetTemporaryFolder(folder string) { 52 | dm.fileAssembler.SetTemporaryFolder(folder) 53 | } 54 | 55 | func (dm *Demuxer) SetOutputFolder(folder string) { 56 | dm.fileAssembler.SetOutputFolder(folder) 57 | } 58 | 59 | func (dm *Demuxer) SetDrawMap(d bool) { 60 | dm.fileAssembler.SetDrawMap(d) 61 | } 62 | 63 | func (dm *Demuxer) SetReprojectImage(r bool) { 64 | dm.fileAssembler.SetReprojectImages(r) 65 | } 66 | 67 | func (dm *Demuxer) SetFalseColor(r bool) { 68 | dm.fileAssembler.SetFalseColor(r) 69 | } 70 | 71 | func (dm *Demuxer) SetMetaFrame(r bool) { 72 | dm.fileAssembler.SetMetaFrame(r) 73 | } 74 | 75 | func (dm *Demuxer) SetEnhance(r bool) { 76 | dm.fileAssembler.SetEnhance(r) 77 | } 78 | 79 | func (dm *Demuxer) SetOnFrameLost(cb func(channelId, currentFrame, lastFrame int)) { 80 | dm.Lock() 81 | dm.cbOnFrameLost = cb 82 | dm.Unlock() 83 | } 84 | 85 | func (dm *Demuxer) SetOnNewVCID(cb func(channelId int)) { 86 | dm.Lock() 87 | dm.cbNewVCID = cb 88 | dm.Unlock() 89 | } 90 | 91 | func (dm *Demuxer) WriteBytes(data []byte) { 92 | dm.frameBuffer = append(dm.frameBuffer, data...) 93 | dm.parse() 94 | } 95 | 96 | func (dm *Demuxer) onMSDU(msdu *MSDU) { 97 | if !dm.shouldSkip(msdu.ChannelId) { 98 | dm.fileAssembler.PutMSDU(msdu) 99 | } 100 | } 101 | 102 | func (dm *Demuxer) incFrameLost(channelId, count int) { 103 | if _, ok := dm.framesLost[channelId]; ok { 104 | dm.framesLost[channelId] += uint64(count) 105 | } else { 106 | dm.framesLost[channelId] = uint64(count) 107 | } 108 | } 109 | 110 | func (dm *Demuxer) checkLostFrameAndSave(channelId, currentFrame int) int { 111 | framesLost := 0 112 | if v, ok := dm.lastFrame[channelId]; ok { 113 | d := int(int64(currentFrame) - int64(v) - 1) 114 | if v > currentFrame { 115 | // TODO: Frame Backwards 116 | } else if d > 0 { 117 | framesLost = d 118 | if dm.cbOnFrameLost != nil { 119 | dm.cbOnFrameLost(channelId, currentFrame, v) 120 | } 121 | } 122 | } 123 | 124 | dm.lastFrame[channelId] = currentFrame 125 | 126 | return framesLost 127 | } 128 | 129 | func (dm *Demuxer) parse() { 130 | for len(dm.frameBuffer) >= dm.frameSize { 131 | frame := dm.frameBuffer[:dm.frameSize] 132 | dm.frameBuffer = dm.frameBuffer[dm.frameSize:] 133 | 134 | cd := MakeVCDU(frame) 135 | 136 | lostFrames := dm.checkLostFrameAndSave(cd.VCID(), cd.Counter()) 137 | 138 | if lostFrames > 0 { 139 | dm.incFrameLost(cd.VCID(), lostFrames) 140 | metrics.DroppedPackets(lostFrames) 141 | } 142 | 143 | if cd.VCID() != 63 { // Skip Fill Channel 144 | if dm.transports[cd.VCID()] == nil { 145 | dm.transports[cd.VCID()] = MakeTransportParser(cd.VCID(), dm.onMSDU) 146 | if dm.cbNewVCID != nil { 147 | dm.cbNewVCID(cd.VCID()) 148 | } 149 | } 150 | metrics.NewPacket() 151 | dm.transports[cd.VCID()].WriteChannelData(cd) 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /ccsds/transportParser.go: -------------------------------------------------------------------------------- 1 | package ccsds 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/opensatelliteproject/SatHelperApp/Logger" 6 | ) 7 | 8 | var skipChannels = []int{63, 2047} 9 | 10 | type TransportParser struct { 11 | id int 12 | cb func(*MSDU) 13 | frameBuffer []byte 14 | msduSize int 15 | tmpMSDU *MSDU 16 | } 17 | 18 | func MakeTransportParser(channelId int, onMSDU func(*MSDU)) *TransportParser { 19 | return &TransportParser{ 20 | id: channelId, 21 | cb: onMSDU, 22 | frameBuffer: make([]byte, 0), 23 | msduSize: 0, 24 | } 25 | } 26 | 27 | func SkipChannel(channelId int) bool { 28 | for _, v := range skipChannels { 29 | if v == channelId { 30 | return true 31 | } 32 | } 33 | 34 | return false 35 | } 36 | 37 | func (tp *TransportParser) closeFrame() { 38 | if tp.tmpMSDU != nil { 39 | tp.tmpMSDU.finalize() 40 | if !SkipChannel(tp.id) && !SkipChannel(tp.tmpMSDU.APID) { // Skip fill packets 41 | msdu := tp.tmpMSDU.Clone() 42 | tp.cb(msdu) 43 | } 44 | } 45 | tp.frameBuffer = make([]byte, 0) 46 | tp.tmpMSDU = nil 47 | } 48 | 49 | func (tp *TransportParser) parseFrame(data []byte) { 50 | if len(data) == 0 { 51 | /* Ignore */ 52 | return 53 | } 54 | //SLog.Debug("TransportParser[%d]::parseFrame([%d]byte)", tp.id, len(data)) 55 | if tp.tmpMSDU != nil { 56 | tp.closeFrame() 57 | } 58 | 59 | if len(data) > 6 { 60 | tp.tmpMSDU = MakeMSDUWithHeader(tp.id, data[:6]) 61 | data = data[6:] 62 | } else { 63 | tp.frameBuffer = data 64 | } 65 | 66 | if tp.tmpMSDU != nil { 67 | data = tp.tmpMSDU.AddBytes(data) 68 | if tp.tmpMSDU.Closed() { 69 | tp.closeFrame() 70 | } 71 | if len(data) > 0 { 72 | tp.parseFrame(data) 73 | } 74 | } 75 | } 76 | 77 | func (tp *TransportParser) WriteChannelData(data *VCDU) { 78 | if data.VCID() != tp.id { 79 | SLog.Error("TransportParser: Wrong channel. Expected %d got %d", data.VCID(), tp.id) 80 | return 81 | } 82 | 83 | if len(data.Data()) != 886 { 84 | SLog.Error("TransportParser: Wrong frame size. Expected %d got %d", 886, len(data.data)) 85 | return 86 | } 87 | 88 | if data.Replay() { 89 | SLog.Warn("Replay Packet: TODO: IMPLEMENT-ME") 90 | // TODO 91 | return 92 | } 93 | 94 | frame := data.Data() 95 | fhp := binary.BigEndian.Uint16(frame[:2]) & 0x7FF 96 | 97 | if fhp != 2047 && fhp > uint16(len(frame)) { 98 | SLog.Error("ERROR: FHP > FRAME") 99 | return 100 | } 101 | 102 | frame = frame[2:] 103 | 104 | if fhp != 2047 { // Has packet start 105 | // There is a header outside the start. Let's split it 106 | a := frame[:fhp] 107 | frame = frame[fhp:] 108 | 109 | if tp.tmpMSDU == nil && len(tp.frameBuffer) > 0 { // Not enough bytes to assemble a header last time 110 | c := append(tp.frameBuffer, a...) 111 | tp.parseFrame(c) 112 | } else if tp.tmpMSDU != nil { 113 | tp.tmpMSDU.AddBytes(a) 114 | tp.closeFrame() 115 | } 116 | 117 | tp.parseFrame(frame) 118 | } else { 119 | if len(tp.frameBuffer) > 0 && tp.tmpMSDU == nil { // Not enough bytes to assemble a header last time 120 | c := append(tp.frameBuffer, frame...) 121 | tp.parseFrame(c) 122 | } else if tp.tmpMSDU == nil { 123 | //SLog.Warn("EDGYEDGY CASE") 124 | //tp.tmpMSDU = MakeMSDUWithHeader(tp.id, frame) 125 | } else { 126 | tp.tmpMSDU.AddBytes(frame) 127 | } 128 | } 129 | 130 | if tp.tmpMSDU != nil && tp.tmpMSDU.Closed() { 131 | tp.closeFrame() 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/multiSegmentDump.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/opensatelliteproject/SatHelperApp" 6 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor" 7 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor/ImageTools" 8 | "github.com/opensatelliteproject/SatHelperApp/Logger" 9 | "github.com/opensatelliteproject/SatHelperApp/Tools" 10 | "github.com/opensatelliteproject/SatHelperApp/XRIT" 11 | "github.com/richardwilkes/toolbox/xio/fs" 12 | "gopkg.in/alecthomas/kingpin.v2" 13 | "io/ioutil" 14 | "path" 15 | "strings" 16 | ) 17 | 18 | func ProcessFile(filename string, ip *ImageProcessor.ImageProcessor) { 19 | SLog.Debug("Processing %s", filename) 20 | xh, err := XRIT.ParseFile(filename) 21 | 22 | if err != nil { 23 | SLog.Error("Error processing file %s: %s", filename, err) 24 | return 25 | } 26 | 27 | b := path.Base(filename) 28 | 29 | if b != xh.Filename() { 30 | p := path.Join(path.Dir(filename), xh.Filename()) 31 | SLog.Info("Moving file %s to %s", filename, p) 32 | err = fs.MoveFile(filename, p) 33 | if err != nil { 34 | SLog.Error("Error moving %s to %s: %s", filename, p, err) 35 | } 36 | filename = p 37 | } 38 | 39 | ImageProcessor.ProcessGOESABI(ip, filename, xh) 40 | } 41 | 42 | // ReplaceAll returns a copy of the string s with all 43 | // non-overlapping instances of old replaced by new. 44 | // If old is empty, it matches at the beginning of the string 45 | // and after each UTF-8 sequence, yielding up to k+1 replacements 46 | // for a k-rune string. 47 | func ReplaceAll(s, old, new string) string { 48 | return strings.Replace(s, old, new, -1) 49 | } 50 | 51 | func main() { 52 | kingpin.Version(SatHelperApp.GetVersion()) 53 | 54 | reproject := kingpin.Flag("linear", "Reproject to linear").Bool() 55 | drawMap := kingpin.Flag("drawMap", "Draw Map Overlay").Bool() 56 | falseColor := kingpin.Flag("falsecolor", "Generate False Color Image").Bool() 57 | metadata := kingpin.Flag("metadata", "Generate Overlays with Metadata").Bool() 58 | enhance := kingpin.Flag("enhance", "Output Enhanced Infrared Images").Bool() 59 | purge := kingpin.Flag("purge", "Purge LRIT files after generating").Bool() 60 | regions := kingpin.Flag("region", "Regions to cut by name (use --list-regions to see all available)").Strings() 61 | listRegions := kingpin.Flag("list-regions", "List all available regions to cut image").Bool() 62 | searchRegion := kingpin.Flag("search-region", "Search for a region").String() 63 | marginPixels := kingpin.Flag("margin-pixels", "Margin Pixels for MapCutter").Default("5").Int() 64 | files := kingpin.Arg("filenames", "File names to dump image").Required().ExistingFilesOrDirs() 65 | 66 | kingpin.Parse() 67 | 68 | mapCutter := ImageTools.GetDefaultMapCutter() 69 | 70 | if *listRegions { 71 | sections := mapCutter.ListSections() 72 | for k, v := range sections { 73 | fmt.Printf("Section Name: \"%s\"\n\t%s\n", k, ReplaceAll(v.String(), "\n", "\n\t")) 74 | } 75 | return 76 | } 77 | 78 | if searchRegion != nil && *searchRegion != "" { 79 | sections := mapCutter.SearchSection(*searchRegion) 80 | for _, k := range sections { 81 | v, _ := mapCutter.GetSection(k) 82 | fmt.Printf("Section Name: \"%s\"\n\t%s\n", k, ReplaceAll(v.String(), "\n", "\n\t")) 83 | } 84 | return 85 | } 86 | 87 | ip := ImageProcessor.MakeImageProcessor() 88 | ip.SetDrawMap(*drawMap) 89 | ip.SetReproject(*reproject) 90 | ip.SetFalseColor(*falseColor) 91 | ip.SetMetadata(*metadata) 92 | ip.SetEnhance(*enhance) 93 | ip.SetCutRegions(*regions) 94 | ip.GetMapCutter().SetMarginPixels(*marginPixels) 95 | 96 | ImageProcessor.SetPurgeFiles(*purge) 97 | 98 | for _, v := range *files { 99 | if Tools.IsDir(v) { 100 | ffiles, err := ioutil.ReadDir(v) 101 | if err != nil { 102 | SLog.Error("Cannot read folder %s: %s", v, err) 103 | continue 104 | } 105 | 106 | for _, v2 := range ffiles { 107 | if !v2.IsDir() && strings.Contains(v2.Name(), ".lrit") { 108 | ProcessFile(path.Join(v, v2.Name()), ip) 109 | } else { 110 | SLog.Debug("Skipping file %s, does not end with .lrit", v2.Name()) 111 | } 112 | } 113 | continue 114 | } 115 | if strings.Contains(v, ".lrit") { 116 | ProcessFile(v, ip) 117 | } else { 118 | SLog.Debug("Skipping file %s, does not end with .lrit", v) 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg000: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg000 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg001 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg002: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg002 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg003: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg003 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg004: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg004 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg005: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg005 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg006: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg006 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg007: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg007 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg008: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg008 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg009: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg009 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg010: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg010 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg011: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg011 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg012: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg012 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg013: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg013 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg014: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg014 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg015: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C02_G16_s20190601900310_e20190601911077_c20190601911151.lritseg015 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg000: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg000 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg001 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg002: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg002 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg003: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg003 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg004: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg004 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg005: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg005 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg006: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg006 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg007: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg007 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg008: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg008 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg009: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg009 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg010: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg010 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg011: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg011 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg012: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg012 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg013: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg013 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg014: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg014 -------------------------------------------------------------------------------- /cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg015: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensatelliteproject/SatHelperApp/b6534dc9fec610f7fc0020738171a220ed30e683/cmd/MultiSegmentDump/testdata/OR_ABI-L2-CMIPF-M3C14_G16_s20190601900310_e20190601911077_c20190601911167.lritseg015 -------------------------------------------------------------------------------- /cmd/SatHelperApp/BaseRPCSource.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/DSP" 5 | "github.com/opensatelliteproject/SatHelperApp/Display" 6 | "github.com/opensatelliteproject/SatHelperApp/RPC/sathelperapp" 7 | ) 8 | 9 | type baseSource struct{} 10 | 11 | var rpcSource = &baseSource{} 12 | 13 | func (s *baseSource) GetStatistics() (*sathelperapp.StatData, error) { 14 | stats := DSP.GetStats() 15 | return &sathelperapp.StatData{ 16 | SignalQuality: uint32(stats.SignalQuality), 17 | 18 | SignalLocked: stats.FrameLock == 1, 19 | ChannelPackets: stats.ReceivedPacketsPerChannel[:], 20 | RsErrors: stats.RsErrors[:], 21 | SyncWord: stats.SyncWord[:], 22 | Scid: int32(stats.SCID), 23 | Vcid: int32(stats.VCID), 24 | DecoderFifoUsage: int32(stats.DecoderFifoUsage), 25 | DemodulatorFifoUsage: int32(stats.DemodulatorFifoUsage), 26 | ViterbiErrors: int32(stats.VitErrors), 27 | FrameSize: int32(stats.FrameBits), 28 | PhaseCorrection: int32(stats.PhaseCorrection), 29 | SyncCorrelation: int32(stats.SyncCorrelation), 30 | CenterFrequency: DSP.Device.GetCenterFrequency() + uint32(DSP.GetCostasFrequency()), 31 | Demuxer: DSP.SDemuxer.GetName(), 32 | Mode: DSP.CurrentConfig.Base.Mode, 33 | Device: DSP.Device.GetName(), 34 | }, nil 35 | } 36 | 37 | func (s *baseSource) GetConsoleLines() (*sathelperapp.ConsoleData, error) { 38 | return &sathelperapp.ConsoleData{ 39 | ConsoleLines: Display.GetConsoleLines(), 40 | }, nil 41 | } 42 | -------------------------------------------------------------------------------- /cmd/SatHelperApp/Config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "github.com/BurntSushi/toml" 8 | "github.com/mewkiz/pkg/osutil" 9 | "github.com/mitchellh/go-homedir" 10 | "github.com/opensatelliteproject/SatHelperApp/DSP" 11 | "github.com/opensatelliteproject/SatHelperApp/Logger" 12 | "io/ioutil" 13 | "log" 14 | "os" 15 | ) 16 | 17 | var configFile = flag.String("config", "", "Configuration File (defaults to $HOME/SatHelperApp/SatHelperApp.cfg)") 18 | var finalConfigFilePath string 19 | 20 | func LoadDefaults(save bool) { 21 | DSP.CurrentConfig.Title = "SatHelperApp" 22 | 23 | DSP.SetHRITMode() 24 | 25 | // Other options 26 | DSP.CurrentConfig.Source.SampleRate = DSP.DefaultSampleRate 27 | DSP.CurrentConfig.Base.Decimation = DSP.DefaultDecimation 28 | DSP.CurrentConfig.Base.AGCEnabled = true 29 | DSP.CurrentConfig.Base.DeviceType = "airspy" 30 | DSP.CurrentConfig.Base.SendConstellation = true 31 | DSP.CurrentConfig.Base.StatisticsPort = DSP.DefaultStatisticsPort 32 | 33 | // Airspy Source Defaults 34 | DSP.CurrentConfig.AirspySource.LNAGain = DSP.DefaultLnaGain 35 | DSP.CurrentConfig.AirspySource.MixerGain = DSP.DefaultMixGain 36 | DSP.CurrentConfig.AirspySource.VGAGain = DSP.DefaultVgaGain 37 | DSP.CurrentConfig.AirspySource.BiasTEnabled = DSP.DefaultBiast 38 | 39 | // CFile Source Defaults 40 | DSP.CurrentConfig.CFileSource.Filename = "" 41 | DSP.CurrentConfig.CFileSource.FastAsPossible = false 42 | 43 | // LimeSDR Source Defaults 44 | DSP.CurrentConfig.LimeSource.LNAGain = 10 45 | DSP.CurrentConfig.LimeSource.Antenna = "LNAH" 46 | 47 | // Spyserver 48 | DSP.CurrentConfig.SpyserverSource.Hostname = "127.0.0.1" 49 | DSP.CurrentConfig.SpyserverSource.Port = 5555 50 | DSP.CurrentConfig.SpyserverSource.Gain = 20 51 | 52 | // Decoder 53 | DSP.CurrentConfig.Decoder.Display = true 54 | DSP.CurrentConfig.Decoder.UseLastFrameData = true 55 | 56 | // Others 57 | DSP.CurrentConfig.Base.DemuxerType = "tcpserver" 58 | 59 | // TCPDemuxer 60 | DSP.CurrentConfig.TCPServerDemuxer.Port = DSP.DefaultVchannelPort 61 | DSP.CurrentConfig.TCPServerDemuxer.Host = "" 62 | 63 | // FileDemuxer 64 | DSP.CurrentConfig.FileDemuxer.Filename = "" 65 | 66 | // Direct Demuxer 67 | DSP.CurrentConfig.DirectDemuxer.OutputFolder = "out" 68 | DSP.CurrentConfig.DirectDemuxer.TemporaryFolder = "tmp" 69 | DSP.CurrentConfig.DirectDemuxer.PurgeFilesAfterProcess = false 70 | DSP.CurrentConfig.DirectDemuxer.SkipVCID = make([]int, 0) 71 | DSP.CurrentConfig.DirectDemuxer.DrawMap = false 72 | DSP.CurrentConfig.DirectDemuxer.ReprojectImages = false 73 | DSP.CurrentConfig.DirectDemuxer.FalseColor = false 74 | DSP.CurrentConfig.DirectDemuxer.Enhanced = false 75 | DSP.CurrentConfig.DirectDemuxer.MetaFrame = true 76 | 77 | // RPC 78 | DSP.CurrentConfig.RPC.Enable = true 79 | DSP.CurrentConfig.RPC.ListenAddr = "" 80 | DSP.CurrentConfig.RPC.ListenPort = DSP.DefaultRPCPort 81 | 82 | // Prometheus 83 | DSP.CurrentConfig.Prometheus.Enable = true 84 | DSP.CurrentConfig.Prometheus.ListenAddr = "" 85 | DSP.CurrentConfig.Prometheus.ListenPort = DSP.DefaultPrometheusPort 86 | 87 | if save { 88 | SaveConfig() 89 | } 90 | } 91 | 92 | func SaveConfig() { 93 | var firstBuffer bytes.Buffer 94 | e := toml.NewEncoder(&firstBuffer) 95 | err := e.Encode(DSP.CurrentConfig) 96 | if err != nil { 97 | log.Printf("Cannot save config: %s", err) 98 | return 99 | } 100 | SLog.Info("Saving config file to %s", finalConfigFilePath) 101 | err = ioutil.WriteFile(finalConfigFilePath, firstBuffer.Bytes(), 0644) 102 | if err != nil { 103 | log.Printf("Cannot save config: %s", err) 104 | return 105 | } 106 | } 107 | 108 | func LoadConfig() { 109 | home, _ := homedir.Dir() 110 | 111 | err := os.MkdirAll(fmt.Sprintf("%s/SatHelperApp", home), os.ModePerm) 112 | if err != nil { 113 | panic(err) 114 | } 115 | 116 | finalConfigFilePath = fmt.Sprintf("%s/SatHelperApp/%s", home, "SatHelperApp.cfg") 117 | 118 | if *configFile != "" { 119 | finalConfigFilePath = *configFile 120 | } 121 | 122 | if !osutil.Exists(finalConfigFilePath) { 123 | SLog.Warn("Config file %s does not exists. Creating one with defaults.", finalConfigFilePath) 124 | LoadDefaults(true) 125 | } else { 126 | SLog.Info("Loading config file from %s", finalConfigFilePath) 127 | _, err = toml.DecodeFile(finalConfigFilePath, &DSP.CurrentConfig) 128 | 129 | if err != nil { 130 | SLog.Warn("Cannot load file SatHelperApp.cfg. Loading default values.") 131 | LoadDefaults(false) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /cmd/SatHelperApp/Tools.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "runtime" 7 | ) 8 | 9 | var clear = map[string]func(){ 10 | "linux": func() { 11 | cmd := exec.Command("clear") //Linux example, its tested 12 | cmd.Stdout = os.Stdout 13 | err := cmd.Run() 14 | if err != nil { 15 | panic(err) 16 | } 17 | }, 18 | "windows": func() { 19 | cmd := exec.Command("cmd", "/c", "cls") //Windows example, its tested 20 | cmd.Stdout = os.Stdout 21 | err := cmd.Run() 22 | if err != nil { 23 | panic(err) 24 | } 25 | }, 26 | } 27 | 28 | func CallClear() { 29 | value, ok := clear[runtime.GOOS] //runtime.GOOS -> linux, windows, darwin etc. 30 | if ok { //if we defined a clear func for that platform: 31 | value() //we execute it 32 | } else { //unsupported platform 33 | clear["linux"]() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /cmd/demuxReplay/.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | out 3 | -------------------------------------------------------------------------------- /cmd/demuxReplay/replay.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp/ccsds" 5 | "log" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | //debugFrames := "/media/ELTN/tmp/demuxdump-1546741011.bin" 11 | debugFrames := "/media/ELTN/tmp/demuxdump-1490627438.bin" 12 | f, err := os.Open(debugFrames) 13 | 14 | if err != nil { 15 | panic(err) 16 | } 17 | defer f.Close() 18 | 19 | finfo, err := f.Stat() 20 | 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | size := finfo.Size() 26 | 27 | dm := ccsds.MakeDemuxer() 28 | dm.SetOnFrameLost(func(channelId, currentFrame, lastFrame int) { 29 | delta := currentFrame - lastFrame 30 | log.Printf("Lost %d frames in channel %d\n", delta, channelId) 31 | }) 32 | 33 | // region Skip DCS 34 | dm.AddSkipVCID(30) 35 | dm.AddSkipVCID(31) 36 | dm.AddSkipVCID(32) 37 | // endregion 38 | 39 | bytesRead := int64(0) 40 | buffer := make([]byte, 892) 41 | 42 | for bytesRead < size { 43 | n, err := f.Read(buffer) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | if n == 892 { 49 | dm.WriteBytes(buffer) 50 | } else { 51 | panic("WAIT") 52 | } 53 | bytesRead += int64(n) 54 | //time.Sleep(time.Millisecond * 10) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /cmd/rpcClient/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/golang/protobuf/ptypes/empty" 8 | "github.com/opensatelliteproject/SatHelperApp" 9 | "github.com/opensatelliteproject/SatHelperApp/RPC/sathelperapp" 10 | "google.golang.org/grpc" 11 | "gopkg.in/alecthomas/kingpin.v2" 12 | "os" 13 | ) 14 | 15 | func GetStatistics(client sathelperapp.InformationClient) { 16 | stats, err := client.GetStatistics(context.Background(), &empty.Empty{}) 17 | if err != nil { 18 | fmt.Printf("Error fetching statistics: %s", err) 19 | os.Exit(1) 20 | } 21 | 22 | data, err := json.MarshalIndent(stats, "", " ") 23 | if err != nil { 24 | fmt.Printf("Error serialzing data: %s", err) 25 | } 26 | 27 | fmt.Println(string(data)) 28 | } 29 | 30 | func GetConsoleLines(client sathelperapp.InformationClient) { 31 | lines, err := client.GetConsoleLines(context.Background(), &empty.Empty{}) 32 | if err != nil { 33 | fmt.Printf("Error fetching statistics: %s", err) 34 | os.Exit(1) 35 | } 36 | 37 | for _, v := range lines.ConsoleLines { 38 | fmt.Println(v) 39 | } 40 | } 41 | 42 | func main() { 43 | kingpin.Version(SatHelperApp.GetVersion()) 44 | 45 | server := kingpin.Flag("server", "Server Address").Default("127.0.0.1:5500").String() 46 | method := kingpin.Arg("method", "Method").Required().String() 47 | kingpin.Parse() 48 | 49 | conn, err := grpc.Dial(*server, grpc.WithInsecure()) 50 | if err != nil { 51 | fmt.Printf("Error connectint to %s: %s", *server, err) 52 | os.Exit(1) 53 | } 54 | defer conn.Close() 55 | 56 | client := sathelperapp.NewInformationClient(conn) 57 | 58 | switch *method { 59 | case "GetStatistics": 60 | GetStatistics(client) 61 | case "GetConsoleLines": 62 | GetConsoleLines(client) 63 | default: 64 | fmt.Printf("Unknown method: %s\n", *method) 65 | os.Exit(1) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /cmd/xritcat/xritcat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/opensatelliteproject/SatHelperApp" 6 | "github.com/opensatelliteproject/SatHelperApp/XRIT" 7 | "gopkg.in/alecthomas/kingpin.v2" 8 | "io" 9 | "io/ioutil" 10 | "os" 11 | ) 12 | 13 | func catFile(filename string) { 14 | xh, err := XRIT.ParseFile(filename) 15 | if err != nil { 16 | fmt.Printf("Error parsing file %s: %s\n", filename, err) 17 | os.Exit(1) 18 | } 19 | offset := xh.PrimaryHeader.HeaderLength 20 | 21 | f, err := os.Open(filename) 22 | 23 | if err != nil { 24 | fmt.Printf("Error parsing file %s: %s\n", filename, err) 25 | os.Exit(1) 26 | } 27 | 28 | _, err = f.Seek(int64(offset), io.SeekStart) 29 | if err != nil { 30 | fmt.Printf("Error parsing file %s: %s\n", filename, err) 31 | os.Exit(1) 32 | } 33 | 34 | data, err := ioutil.ReadAll(f) 35 | if err != nil { 36 | fmt.Printf("Error parsing file %s: %s\n", filename, err) 37 | os.Exit(1) 38 | } 39 | 40 | _, _ = os.Stdout.Write(data) 41 | } 42 | 43 | func main() { 44 | kingpin.Version(SatHelperApp.GetVersion()) 45 | 46 | files := kingpin.Arg("filename", "File name to print content").Required().ExistingFiles() 47 | 48 | kingpin.Parse() 49 | 50 | for _, v := range *files { 51 | catFile(v) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cmd/xritimg/xritimg.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/opensatelliteproject/SatHelperApp" 5 | "github.com/opensatelliteproject/SatHelperApp/ImageProcessor/ImageTools" 6 | "github.com/opensatelliteproject/SatHelperApp/Logger" 7 | "gopkg.in/alecthomas/kingpin.v2" 8 | ) 9 | 10 | func main() { 11 | kingpin.Version(SatHelperApp.GetVersion()) 12 | 13 | files := kingpin.Arg("filename", "File name to dump image").Required().ExistingFiles() 14 | 15 | kingpin.Parse() 16 | 17 | for _, v := range *files { 18 | err := ImageTools.DumpImage(v) 19 | if err != nil { 20 | SLog.Error("Error processing %s: %s", v, err) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cmd/xritparse/printers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 7 | "github.com/opensatelliteproject/SatHelperApp/XRIT/Structs" 8 | "strings" 9 | ) 10 | 11 | func PrintAncillaryText(r *Structs.AncillaryText, printStructuredHeader, printImageDataRecord bool) { 12 | fmt.Println("Ancillary Record:") 13 | vs := strings.Split(r.StringData, ";") 14 | fmt.Println(" Data:") 15 | for _, v := range vs { 16 | if len(v) > 0 { 17 | fmt.Printf(" %s;\n", v) 18 | } 19 | } 20 | } 21 | 22 | func PrintAnnotationRecord(r *Structs.AnnotationRecord, printStructuredHeader, printImageDataRecord bool) { 23 | fmt.Println("Annotation Record:") 24 | fmt.Printf(" FileName: %s\n", r.StringData) 25 | } 26 | 27 | func PrintDCSFilenameRecord(r *Structs.DCSFilenameRecord, printStructuredHeader, printImageDataRecord bool) { 28 | fmt.Println("DCS FileName Record:") 29 | fmt.Printf(" FileName: %s\n", r.StringData) 30 | } 31 | 32 | func PrintHeaderStructuredRecord(r *Structs.HeaderStructuredRecord, printStructuredHeader, printImageDataRecord bool) { 33 | fmt.Println("Header Structured Record:") 34 | if printStructuredHeader { 35 | t := strings.Split(r.StringData, "UI") 36 | fmt.Println(" Data:") 37 | for _, v := range t { 38 | fmt.Printf(" %s\n", v) 39 | } 40 | } else { 41 | fmt.Println(" Data: {HIDDEN}") 42 | } 43 | } 44 | 45 | func PrintImageDataFunctionRecord(r *Structs.ImageDataFunctionRecord, printStructuredHeader, printImageDataRecord bool) { 46 | fmt.Println("Image Data Function Record:") 47 | if printImageDataRecord { 48 | fmt.Printf(" Data: %s\n", r.StringData) 49 | } else { 50 | fmt.Println(" Data: {HIDDEN}") 51 | } 52 | } 53 | 54 | func PrintImageNavigationRecord(r *Structs.ImageNavigationRecord, printStructuredHeader, printImageDataRecord bool) { 55 | fmt.Println("Image Navigation Record:") 56 | fmt.Printf(" Projection Name: %s\n", r.ProjectionName) 57 | fmt.Printf(" Column Scaling Factor: %d\n", r.ColumnScalingFactor) 58 | fmt.Printf(" Line Scaling Factor: %d\n", r.LineScalingFactor) 59 | fmt.Printf(" Column Offset: %d\n", r.ColumnOffset) 60 | fmt.Printf(" Line Offset: %d\n", r.LineOffset) 61 | } 62 | 63 | func PrintImageStructureRecord(r *Structs.ImageStructureRecord, printStructuredHeader, printImageDataRecord bool) { 64 | fmt.Println("Image Structure Header:") 65 | fmt.Printf(" Bits Per Pixel: %d\n", r.BitsPerPixel) 66 | fmt.Printf(" Columns: %d\n", r.Columns) 67 | fmt.Printf(" Lines: %d\n", r.Lines) 68 | fmt.Printf(" Compression: %s", PacketData.GetCompressionTypeString(int(r.Compression))) 69 | } 70 | 71 | func PrintNOAASpecificRecord(r *Structs.NOAASpecificRecord, printStructuredHeader, printImageDataRecord bool) { 72 | fmt.Println("NOAA Specific Header:") 73 | fmt.Printf(" Signature: %s\n", r.Signature) 74 | fmt.Printf(" Product ID: %s (%d)\n", r.Product().Name, r.ProductID) 75 | fmt.Printf(" Sub Product ID: %s (%d)\n", r.SubProduct().Name, r.ProductSubID) 76 | fmt.Printf(" Compression: %s\n", PacketData.GetCompressionTypeString(int(r.Compression))) 77 | fmt.Printf(" Parameter: %d\n", r.Parameter) 78 | } 79 | 80 | func PrintPrimaryRecord(r *Structs.PrimaryRecord, printStructuredHeader, printImageDataRecord bool) { 81 | fmt.Println("Primary Header:") 82 | fmt.Printf(" FileTypeCode: %s\n", PacketData.GetFileTypeCodeString(int(r.FileTypeCode))) 83 | fmt.Printf(" Header Length: %d\n", r.HeaderLength) 84 | fmt.Printf(" Data Length: %d\n", r.DataLength) 85 | } 86 | 87 | func PrintRiceCompressionRecord(r *Structs.RiceCompressionRecord, printStructuredHeader, printImageDataRecord bool) { 88 | fmt.Println("Rice Compression Record:") 89 | fmt.Printf(" Flags: %d\n", r.Flags) 90 | fmt.Printf(" Pixel: %d\n", r.Pixel) 91 | fmt.Printf(" Line: %d\n", r.Line) 92 | } 93 | 94 | func PrintSegmentIdentificationRecord(r *Structs.SegmentIdentificationRecord, printStructuredHeader, printImageDataRecord bool) { 95 | fmt.Println("Segment Identification Header:") 96 | fmt.Printf(" ImageID: %d\n", r.ImageID) 97 | fmt.Printf(" Sequence: %d\n", r.Sequence) 98 | fmt.Printf(" Start Column: %d\n", r.StartColumn) 99 | fmt.Printf(" Start Line: %d\n", r.StartLine) 100 | fmt.Printf(" Number of Segments: %d\n", r.MaxSegments) 101 | fmt.Printf(" Width: %d\n", r.MaxColumns) 102 | fmt.Printf(" Height: %d\n", r.MaxRows) 103 | } 104 | func PrintTimestampRecord(r *Structs.TimestampRecord, printStructuredHeader, printImageDataRecord bool) { 105 | fmt.Println("Timestamp Record:") 106 | fmt.Printf(" Days: %d\n", r.Days) 107 | fmt.Printf(" Milisseconds: %d\n", r.Milisseconds) 108 | fmt.Printf(" DateTime: %s\n", r.GetDateTime()) 109 | } 110 | 111 | func PrintUnknownHeader(r *Structs.UnknownHeader, printStructuredHeader, printImageDataRecord bool) { 112 | fmt.Printf("Unknown Header (%d):", r.Type) 113 | v := strings.ToUpper(hex.EncodeToString(r.Data)) 114 | fmt.Printf(" Hex Encoded Data: %s\n", v) 115 | } 116 | -------------------------------------------------------------------------------- /cmd/xritparse/xritparse.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/opensatelliteproject/SatHelperApp" 6 | "github.com/opensatelliteproject/SatHelperApp/XRIT" 7 | "github.com/opensatelliteproject/SatHelperApp/XRIT/PacketData" 8 | "github.com/opensatelliteproject/SatHelperApp/XRIT/Structs" 9 | "gopkg.in/alecthomas/kingpin.v2" 10 | "os" 11 | ) 12 | 13 | func printHeaders(header *XRIT.Header, printStructuredHeader, printImageDataRecord bool) { 14 | for _, v := range header.AllHeaders { 15 | switch v.GetType() { 16 | case PacketData.AncillaryTextRecord: 17 | PrintAncillaryText(v.(*Structs.AncillaryText), printStructuredHeader, printImageDataRecord) 18 | case PacketData.AnnotationRecord: 19 | PrintAnnotationRecord(v.(*Structs.AnnotationRecord), printStructuredHeader, printImageDataRecord) 20 | case PacketData.DCSFileNameRecord: 21 | PrintDCSFilenameRecord(v.(*Structs.DCSFilenameRecord), printStructuredHeader, printImageDataRecord) 22 | case PacketData.HeaderStructuredRecord: 23 | PrintHeaderStructuredRecord(v.(*Structs.HeaderStructuredRecord), printStructuredHeader, printImageDataRecord) 24 | case PacketData.ImageDataFunctionRecord: 25 | PrintImageDataFunctionRecord(v.(*Structs.ImageDataFunctionRecord), printStructuredHeader, printImageDataRecord) 26 | case PacketData.ImageNavigationRecord: 27 | PrintImageNavigationRecord(v.(*Structs.ImageNavigationRecord), printStructuredHeader, printImageDataRecord) 28 | case PacketData.ImageStructureRecord: 29 | PrintImageStructureRecord(v.(*Structs.ImageStructureRecord), printStructuredHeader, printImageDataRecord) 30 | case PacketData.NOAASpecificHeader: 31 | PrintNOAASpecificRecord(v.(*Structs.NOAASpecificRecord), printStructuredHeader, printImageDataRecord) 32 | case PacketData.PrimaryHeader: 33 | PrintPrimaryRecord(v.(*Structs.PrimaryRecord), printStructuredHeader, printImageDataRecord) 34 | case PacketData.RiceCompressionRecord: 35 | PrintRiceCompressionRecord(v.(*Structs.RiceCompressionRecord), printStructuredHeader, printImageDataRecord) 36 | case PacketData.SegmentIdentificationRecord: 37 | PrintSegmentIdentificationRecord(v.(*Structs.SegmentIdentificationRecord), printStructuredHeader, printImageDataRecord) 38 | case PacketData.TimestampRecord: 39 | PrintTimestampRecord(v.(*Structs.TimestampRecord), printStructuredHeader, printImageDataRecord) 40 | default: 41 | PrintUnknownHeader(v.(*Structs.UnknownHeader), printStructuredHeader, printImageDataRecord) 42 | } 43 | fmt.Println("") 44 | } 45 | } 46 | 47 | func parseFile(filename string, printStructuredHeader, printImageDataRecord bool) { 48 | xh, err := XRIT.ParseFile(filename) 49 | if err != nil { 50 | fmt.Printf("Error parsing file %s: %s\n", filename, err) 51 | os.Exit(1) 52 | } 53 | 54 | printHeaders(xh, printStructuredHeader, printImageDataRecord) 55 | } 56 | 57 | func main() { 58 | kingpin.Version(SatHelperApp.GetVersion()) 59 | 60 | files := kingpin.Arg("filename", "File name to parse").Required().ExistingFiles() 61 | 62 | printStructuredHeader := kingpin.Flag("h", "Print Structured Header Record").Default("false").Bool() 63 | printImageDataRecord := kingpin.Flag("i", "Print Image Data Record").Default("false").Bool() 64 | 65 | kingpin.Parse() 66 | 67 | for _, v := range *files { 68 | parseFile(v, *printStructuredHeader, *printImageDataRecord) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /cmd/xritpdcs/xritpdcs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/opensatelliteproject/SatHelperApp" 6 | "github.com/opensatelliteproject/SatHelperApp/XRIT" 7 | "github.com/opensatelliteproject/SatHelperApp/XRIT/Structs" 8 | "gopkg.in/alecthomas/kingpin.v2" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | ) 13 | 14 | func catFile(filename string) { 15 | xh, err := XRIT.ParseFile(filename) 16 | if err != nil { 17 | fmt.Printf("Error parsing file %s: %s\n", filename, err) 18 | os.Exit(1) 19 | } 20 | offset := xh.PrimaryHeader.HeaderLength 21 | 22 | f, err := os.Open(filename) 23 | 24 | if err != nil { 25 | fmt.Printf("Error parsing file %s: %s\n", filename, err) 26 | os.Exit(1) 27 | } 28 | 29 | _, err = f.Seek(int64(offset), io.SeekStart) 30 | if err != nil { 31 | fmt.Printf("Error parsing file %s: %s\n", filename, err) 32 | os.Exit(1) 33 | } 34 | 35 | data, err := ioutil.ReadAll(f) 36 | if err != nil { 37 | fmt.Printf("Error parsing file %s: %s\n", filename, err) 38 | os.Exit(1) 39 | } 40 | 41 | d := Structs.ParseDCS(data) 42 | fmt.Printf("Header: %s\n", d.Header) 43 | fmt.Println(" # Address Date / Time Status Signal Frequency Offset MIN DQN Channel Source ") 44 | for i, v := range d.Packets { 45 | fmt.Printf("%4d %8s %29s %1s %2d dB %2d %1s %1s %4s %2s \n", i, v.Address, v.DateTime, v.Status, v.Signal, v.FrequencyOffset, v.ModIndexNormal, v.DataQualNominal, v.Channel, v.SourceCode) 46 | } 47 | } 48 | 49 | func main() { 50 | kingpin.Version(SatHelperApp.GetVersion()) 51 | 52 | files := kingpin.Arg("filename", "DCS File name to print content").Required().ExistingFiles() 53 | 54 | kingpin.Parse() 55 | 56 | for _, v := range *files { 57 | catFile(v) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/opensatelliteproject/SatHelperApp 2 | 3 | require ( 4 | github.com/BurntSushi/toml v0.3.1 5 | github.com/airking05/termui v2.2.0+incompatible 6 | github.com/go-stack/stack v1.8.0 // indirect 7 | github.com/gogo/protobuf v1.1.1 // indirect 8 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 9 | github.com/golang/protobuf v1.3.2 10 | github.com/jonas-p/go-shp v0.1.1 11 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect 12 | github.com/llgcode/draw2d v0.0.0-20180825133448-f52c8a71aff0 13 | github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e 14 | github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 15 | github.com/maruel/panicparse v1.1.1 // indirect 16 | github.com/mattn/go-runewidth v0.0.4 // indirect 17 | github.com/mewkiz/pkg v0.0.0-20190222151137-b7948a1ad1b1 18 | github.com/mitchellh/go-homedir v1.1.0 19 | github.com/mitchellh/go-wordwrap v1.0.0 // indirect 20 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 21 | github.com/modern-go/reflect2 v1.0.1 // indirect 22 | github.com/myriadrf/limedrv v0.0.0-20190225221912-8583a26e3fce 23 | github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d 24 | github.com/opensatelliteproject/goaec v0.0.0-20190224065807-d814e01b69fa 25 | github.com/opensatelliteproject/libsathelper v0.0.0-20190224071010-9df110e77627 26 | github.com/prometheus/client_golang v1.1.0 27 | github.com/prometheus/common v0.6.0 28 | github.com/quan-to/slog v0.0.0-20190317205605-56a2b4159924 29 | github.com/racerxdl/fastconvert v0.0.0-20190129064530-871b6f6cd82a 30 | github.com/racerxdl/go.fifo v0.0.0-20180604061744-c6aa83afe374 31 | github.com/racerxdl/segdsp v0.0.0-20190329062126-4f400f793b40 32 | github.com/racerxdl/spy2go v0.0.0-20190103011754-14102c047be5 33 | github.com/richardwilkes/toolbox v1.10.0 34 | github.com/sirupsen/logrus v1.3.0 // indirect 35 | golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a // indirect 36 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 37 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 38 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 39 | google.golang.org/grpc v1.19.1 40 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 41 | ) 42 | -------------------------------------------------------------------------------- /metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | "github.com/prometheus/client_golang/prometheus/promhttp" 6 | "net/http" 7 | ) 8 | 9 | var ( 10 | droppedPackets = prometheus.NewCounter(prometheus.CounterOpts{ 11 | Name: "sathelperapp_droppedpackets", 12 | Help: "Number of dropped packets", 13 | }) 14 | decoderFifoUsage = prometheus.NewGauge(prometheus.GaugeOpts{ 15 | Name: "sathelperapp_decoderfifousage", 16 | Help: "Decoder FIFO usage in Percent", 17 | }) 18 | demodulatorFifoUsage = prometheus.NewGauge(prometheus.GaugeOpts{ 19 | Name: "sathelperapp_demodulatorfifousage", 20 | Help: "Demodulator FIFO usage in Percent", 21 | }) 22 | viterbi = prometheus.NewGauge(prometheus.GaugeOpts{ 23 | Name: "sathelperapp_viterbi", 24 | Help: "The total number of corrected bits by Viterbi", 25 | }) 26 | viterbiHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{ 27 | Name: "sathelperapp_viterbihist", 28 | Help: "The Viterbi corrected bits", 29 | }) 30 | signalQuality = prometheus.NewGauge(prometheus.GaugeOpts{ 31 | Name: "sathelperapp_signalquality", 32 | Help: "The signal quality in percent", 33 | }) 34 | signalStatus = prometheus.NewGauge(prometheus.GaugeOpts{ 35 | Name: "sathelperapp_signalstatus", 36 | Help: "The signal status: 1 for locked, 0 for not locked", 37 | }) 38 | syncCorrelation = prometheus.NewGauge(prometheus.GaugeOpts{ 39 | Name: "sathelperapp_syncorrelation", 40 | Help: "The number of matched bits in sync correlation", 41 | }) 42 | reedSolomon = prometheus.NewGauge(prometheus.GaugeOpts{ 43 | Name: "sathelperapp_reedsolomon", 44 | Help: "The number of corrected bytes by ReedSolomon", 45 | }) 46 | totalFiles = prometheus.NewCounter(prometheus.CounterOpts{ 47 | Name: "sathelperapp_totalfiles", 48 | Help: "The number of total files received", 49 | }) 50 | totalPackets = prometheus.NewCounter(prometheus.CounterOpts{ 51 | Name: "sathelperapp_totalpackets", 52 | Help: "The number of total packets received", 53 | }) 54 | 55 | prometheusEnabled = false 56 | ) 57 | 58 | // DroppedPacket add specified number of packets to dropped packets 59 | func DroppedPackets(packets int) { 60 | if prometheusEnabled { 61 | droppedPackets.Add(float64(packets)) 62 | } 63 | } 64 | 65 | // DecoderFifoUsage sets the Decoder fifo usage 66 | func DecoderFifoUsage(usage float64) { 67 | if prometheusEnabled { 68 | decoderFifoUsage.Set(usage) 69 | } 70 | } 71 | 72 | func DemodulatorFifoUsage(usage float64) { 73 | if prometheusEnabled { 74 | demodulatorFifoUsage.Set(usage) 75 | } 76 | } 77 | 78 | func Viterbi(vit int) { 79 | if prometheusEnabled { 80 | viterbi.Set(float64(vit)) 81 | viterbiHistogram.Observe(float64(vit)) 82 | } 83 | } 84 | 85 | func SignalQuality(quality int) { 86 | if prometheusEnabled { 87 | signalQuality.Set(float64(quality)) 88 | } 89 | } 90 | 91 | func SignalStatus(locked bool) { 92 | if prometheusEnabled { 93 | if locked { 94 | signalStatus.Set(1) 95 | } else { 96 | signalStatus.Set(0) 97 | } 98 | } 99 | } 100 | 101 | func SyncCorrelation(correlation int) { 102 | if prometheusEnabled { 103 | syncCorrelation.Set(float64(correlation)) 104 | } 105 | } 106 | 107 | func ReedSolomon(rs int) { 108 | if prometheusEnabled { 109 | reedSolomon.Set(float64(rs)) 110 | } 111 | } 112 | 113 | func NewFile() { 114 | if prometheusEnabled { 115 | totalFiles.Inc() 116 | } 117 | } 118 | 119 | func NewPacket() { 120 | if prometheusEnabled { 121 | totalPackets.Inc() 122 | } 123 | } 124 | 125 | var registry *prometheus.Registry 126 | 127 | // EnablePrometheus enable metric collection in prometheus 128 | func EnablePrometheus() { 129 | if !prometheusEnabled { 130 | prometheusEnabled = true 131 | 132 | registry = prometheus.NewRegistry() 133 | registry.MustRegister(droppedPackets) 134 | registry.MustRegister(decoderFifoUsage) 135 | registry.MustRegister(demodulatorFifoUsage) 136 | registry.MustRegister(viterbi) 137 | registry.MustRegister(viterbiHistogram) 138 | registry.MustRegister(signalQuality) 139 | registry.MustRegister(signalStatus) 140 | registry.MustRegister(syncCorrelation) 141 | registry.MustRegister(reedSolomon) 142 | registry.MustRegister(totalPackets) 143 | registry.MustRegister(totalFiles) 144 | } 145 | } 146 | 147 | func GetHandler() http.Handler { 148 | if prometheusEnabled { 149 | return promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) 150 | } 151 | return nil 152 | } 153 | -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: sathelperapp 2 | version: git 3 | version-script: cat version 4 | summary: LRIT/HRIT Demodulator / Decoder 5 | description: | 6 | The OpenSatelliteProject Satellite Helper Application! This is currently a LRIT/HRIT Demodulator / Decoder program 7 | 8 | grade: stable 9 | confinement: strict 10 | 11 | apps: 12 | sathelperapp: 13 | plugs: [network-bind, network, home, raw-usb] 14 | command: SatHelperApp 15 | SatHelperApp: # Alias 16 | plugs: [network-bind, network, home, raw-usb] 17 | command: SatHelperApp 18 | DemuxReplay: 19 | command: DemuxReplay 20 | xritparse: 21 | command: xritparse 22 | xritcat: 23 | command: xritcat 24 | xritimg: 25 | command: xritimg 26 | xritpdcs: 27 | command: xritpdcs 28 | SatHelperDump: 29 | command: SatHelperDump 30 | SatHelperClient: 31 | plugs: [network-bind, network, home] 32 | command: SatHelperClient 33 | 34 | parts: 35 | ppaadd: 36 | plugin: nil 37 | override-build: | 38 | echo "deb http://ppa.launchpad.net/opensatelliteproject/ppa/ubuntu xenial main" | tee /etc/apt/sources.list.d/opensatelliteproject.list 39 | echo "deb http://ppa.launchpad.net/opensatelliteproject/drivers/ubuntu xenial main" | tee /etc/apt/sources.list.d/drivers.list 40 | echo "deb http://ppa.launchpad.net/gophers/archive/ubuntu xenial main" | tee /etc/apt/sources.list.d/gophers-archive.list 41 | apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 496DD3727263A7A279F9B349407E8BB01C195687 42 | apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 11FC2E68126782B43762694F22C627172ECB91FE 43 | apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C73998DC9DFEA6DCF1241057308C15A29AD198E9 44 | apt update 45 | apt install -yy libaec-dev libaec0 libcorrect libsathelper libsoapysdr0.6 libairspy0 libsoapysdr-dev libairspy-dev git g++ cmake libsqlite3-dev libi2c-dev libusb-1.0-0-dev swig swig3.0 golang-1.11 46 | echo "Building RTLSDR" 47 | [ ! -d "librtlsdr" ] && git clone https://github.com/librtlsdr/librtlsdr.git 48 | cd librtlsdr && mkdir build && cd build && cmake .. && make -j10 && make install && ldconfig && cd ../.. 49 | echo "Building Static LimeSuite" 50 | [ ! -d "LimeSuite" ] && git clone https://github.com/myriadrf/LimeSuite.git 51 | cd LimeSuite && git checkout stable && mkdir builddir && cd builddir && cmake ../ -DBUILD_SHARED_LIBS=OFF && make -j10 && make install && rm -fr * && cmake ../ && make -j10 && make install && ldconfig && cd ../.. 52 | prime: [-*] 53 | 54 | sathelperapp: 55 | source: . 56 | plugin: make 57 | after: [ppaadd] 58 | build-packages: 59 | - libusb-1.0-0 60 | - swig 61 | - swig3.0 62 | stage-packages: 63 | - libusb-1.0-0 64 | - libstdc++6 65 | - libc6 66 | - libudev1 67 | - libgcc1 68 | 69 | -------------------------------------------------------------------------------- /travis-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REV_VAR="github.com/opensatelliteproject/SatHelperApp.RevString" 4 | VERSION_VAR="github.com/opensatelliteproject/SatHelperApp.VersionString" 5 | BUILD_DATE_VAR="github.com/opensatelliteproject/SatHelperApp.CompilationDate" 6 | BUILD_TIME_VAR="github.com/opensatelliteproject/SatHelperApp.CompilationTime" 7 | REPO_VERSION=$(git describe --always --dirty --tags) 8 | REPO_REV=$(git rev-parse HEAD) 9 | BUILD_DATE=$(date +"%d%m%Y") 10 | BUILD_TIME=$(date +"%H%M%S") 11 | GOBUILD_VERSION_ARGS="-ldflags \"-X '${REV_VAR}=${REPO_REV}' -X '${VERSION_VAR}=${REPO_VERSION}' -X '${BUILD_DATE_VAR}=${BUILD_DATE}' -X '${BUILD_TIME_VAR}=${BUILD_TIME}'\"" 12 | 13 | echo "REV_VAR=${REV_VAR}" 14 | echo "VERSION_VAR=${VERSION_VAR}" 15 | echo "BUILD_DATE_VAR=${BUILD_DATE_VAR}" 16 | echo "BUILD_TIME_VAR=${BUILD_TIME_VAR}" 17 | echo "REPO_VERSION=${REPO_VERSION}" 18 | echo "REPO_REV=${REPO_REV}" 19 | echo "BUILD_DATE=${BUILD_DATE}" 20 | echo "BUILD_TIME=${BUILD_TIME}" 21 | echo "GOBUILD_VERSION_ARGS=${GOBUILD_VERSION_ARGS}" 22 | 23 | export GO111MODULE=on 24 | 25 | TAG=`git describe --exact-match --tags HEAD 2>/dev/null` 26 | if [[ $? -eq 0 ]]; 27 | then 28 | echo "Releasing for tag ${TAG}" 29 | ORIGINAL_FOLDER="`pwd`" 30 | echo "I'm in `pwd`" 31 | mkdir -p bins 32 | mkdir -p zips 33 | 34 | echo "Building RTLSDR" 35 | git clone https://github.com/librtlsdr/librtlsdr.git 36 | cd librtlsdr 37 | mkdir -p build && cd build 38 | cmake .. 39 | make -j10 40 | sudo make install 41 | sudo ldconfig 42 | cd .. 43 | 44 | echo "Going back to $ORIGINAL_FOLDER" 45 | cd "$ORIGINAL_FOLDER" 46 | 47 | echo "Building Static LimeSuite" 48 | git clone https://github.com/myriadrf/LimeSuite.git 49 | cd LimeSuite 50 | git checkout stable 51 | mkdir -p builddir && cd builddir 52 | cmake ../ -DBUILD_SHARED_LIBS=OFF 53 | make -j10 54 | sudo make install 55 | sudo ldconfig 56 | cd .. 57 | 58 | echo "Going back to $ORIGINAL_FOLDER" 59 | cd "$ORIGINAL_FOLDER" 60 | 61 | echo "Updating Code to have static libLimeSuite" 62 | sed -i 's/-lLimeSuite/-l:libLimeSuite.a -l:libstdc++.a -static-libgcc -lm -lusb-1.0/g' $(GOPATH)/pkg/mod/github.com/myriadrf/limedrv*/limewrap/limewrap.go 63 | 64 | echo "Building" 65 | cd cmd 66 | for i in * 67 | do 68 | echo "Building $i" 69 | cd ${i} 70 | echo go build ${GOBUILD_VERSION_ARGS} -o ../../bins/${i} 71 | bash -c "go build ${GOBUILD_VERSION_ARGS} -o ../../bins/${i}" 72 | echo "Zipping ${i}-${TAG}-linux-amd64.zip" 73 | zip -r "../../zips/${i}-${TAG}-linux-amd64.zip" ../../bins/$i 74 | cd .. 75 | done 76 | cd .. 77 | echo "Binaries: " 78 | ls -la bins 79 | echo "Zip Files: " 80 | ls -la zips 81 | else 82 | echo "No tags for current commit. Skipping releases." 83 | fi -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | 0.5.2 2 | --------------------------------------------------------------------------------