From 7e08447837822da19b6beacab5b3bf471b5a7b2f Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 27 Nov 2021 10:50:41 +0100 Subject: [PATCH] Support for ChirpChat mod and demod and IEEE802.15.4 mod --- package.json | 2 +- src/app/app.component.ts | 2 +- .../channel-details-routing.module.ts | 15 + .../channel-details.component.ts | 6 + .../channel-details/channel-details.module.ts | 8 +- src/app/channel-details/channel-details.ts | 10 + .../chirpchat-demod.component.css | 114 ++++ .../chirpchat-demod.component.html | 317 ++++++++++ .../chirpchat-demod.component.spec.ts | 58 ++ .../chirpchat-demod.component.ts | 471 +++++++++++++++ .../chirpchat-demod/chirpchat-demod.ts | 93 +++ .../chirpchat-mod/chirpchat-mod.component.css | 107 ++++ .../chirpchat-mod.component.html | 328 +++++++++++ .../chirpchat-mod.component.spec.ts | 57 ++ .../chirpchat-mod/chirpchat-mod.component.ts | 470 +++++++++++++++ .../chirpchat-mod/chirpchat-mod.ts | 95 +++ .../ieee802154-mod.component.css | 91 +++ .../ieee802154-mod.component.html | 366 ++++++++++++ .../ieee802154-mod.component.spec.ts | 58 ++ .../ieee802154-mod.component.ts | 556 ++++++++++++++++++ .../ieee802154-mod/ieee802154-mod.ts | 100 ++++ 21 files changed, 3321 insertions(+), 3 deletions(-) create mode 100644 src/app/channel-details/chirpchat-demod/chirpchat-demod.component.css create mode 100644 src/app/channel-details/chirpchat-demod/chirpchat-demod.component.html create mode 100644 src/app/channel-details/chirpchat-demod/chirpchat-demod.component.spec.ts create mode 100644 src/app/channel-details/chirpchat-demod/chirpchat-demod.component.ts create mode 100644 src/app/channel-details/chirpchat-demod/chirpchat-demod.ts create mode 100644 src/app/channel-details/chirpchat-mod/chirpchat-mod.component.css create mode 100644 src/app/channel-details/chirpchat-mod/chirpchat-mod.component.html create mode 100644 src/app/channel-details/chirpchat-mod/chirpchat-mod.component.spec.ts create mode 100644 src/app/channel-details/chirpchat-mod/chirpchat-mod.component.ts create mode 100644 src/app/channel-details/chirpchat-mod/chirpchat-mod.ts create mode 100644 src/app/channel-details/ieee802154-mod/ieee802154-mod.component.css create mode 100644 src/app/channel-details/ieee802154-mod/ieee802154-mod.component.html create mode 100644 src/app/channel-details/ieee802154-mod/ieee802154-mod.component.spec.ts create mode 100644 src/app/channel-details/ieee802154-mod/ieee802154-mod.component.ts create mode 100644 src/app/channel-details/ieee802154-mod/ieee802154-mod.ts diff --git a/package.json b/package.json index fabe631..b6d78f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sdrangelcli", - "version": "2.3.0", + "version": "2.4.0", "repository": { "type": "git", "url": "git://github.com/f4exb/sdrangelcli.git" diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 42d36f8..cf1d468 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -16,7 +16,7 @@ import { RemoveFeaturesetDialogComponent } from './main/remove-featureset-dialog export class AppComponent { title = 'SDRangelCli'; - version = '2.3.0'; + version = '2.4.0'; sdrangelURL = 'http://127.0.0.1:8091/sdrangel'; // the default URL constructor(private sdrangelUrlService: SdrangelUrlService, diff --git a/src/app/channel-details/channel-details-routing.module.ts b/src/app/channel-details/channel-details-routing.module.ts index b8310f2..2312261 100644 --- a/src/app/channel-details/channel-details-routing.module.ts +++ b/src/app/channel-details/channel-details-routing.module.ts @@ -32,6 +32,9 @@ import { PacketDemodComponent } from './packet-demod/packet-demod.component'; import { PagerDemodComponent } from './pager-demod/pager-demod.component'; import { AisModComponent } from './ais-mod/ais-mod.component'; import { PacketModComponent } from './packet-mod/packet-mod.component'; +import { Ieee802154ModComponent } from './ieee802154-mod/ieee802154-mod.component'; +import { ChirpchatModComponent } from './chirpchat-mod/chirpchat-mod.component'; +import { ChirpchatDemodComponent } from './chirpchat-demod/chirpchat-demod.component'; export const routes: Routes = [ { @@ -58,6 +61,14 @@ export const routes: Routes = [ path: 'bfmdemod', component: BfmDemodComponent }, + { + path: 'chirpchatdemod', + component: ChirpchatDemodComponent + }, + { + path: 'chirpchatmod', + component: ChirpchatModComponent + }, { path: 'filesink', component: FileSinkComponent @@ -78,6 +89,10 @@ export const routes: Routes = [ path: 'freqtracker', component: FreqtrackerSinkComponent }, + { + path: 'ieee802154mod', + component: Ieee802154ModComponent + }, { path: 'localsink', component: LocalSinkComponent diff --git a/src/app/channel-details/channel-details.component.ts b/src/app/channel-details/channel-details.component.ts index f2c2b91..90d4cc3 100644 --- a/src/app/channel-details/channel-details.component.ts +++ b/src/app/channel-details/channel-details.component.ts @@ -48,6 +48,10 @@ export class ChannelDetailsComponent implements OnInit { this.router.navigate(['amdemod'], { relativeTo: this.route}); } else if (channelSettings.channelType === 'BFMDemod') { this.router.navigate(['bfmdemod'], { relativeTo: this.route}); + } else if (channelSettings.channelType === 'ChirpChatDemod') { + this.router.navigate(['chirpchatdemod'], { relativeTo: this.route}); + } else if (channelSettings.channelType === 'ChirpChatMod') { + this.router.navigate(['chirpchatmod'], { relativeTo: this.route}); } else if (channelSettings.channelType === 'FileSink') { this.router.navigate(['filesink'], { relativeTo: this.route}); } else if (channelSettings.channelType === 'FileSource') { @@ -58,6 +62,8 @@ export class ChannelDetailsComponent implements OnInit { this.router.navigate(['freedvdemod'], { relativeTo: this.route}); } else if (channelSettings.channelType === 'FreqTracker') { this.router.navigate(['freqtracker'], { relativeTo: this.route}); + } else if (channelSettings.channelType === 'IEEE_802_15_4_Mod') { + this.router.navigate(['ieee802154mod'], { relativeTo: this.route}); } else if (channelSettings.channelType === 'LocalSink') { this.router.navigate(['localsink'], { relativeTo: this.route}); } else if (channelSettings.channelType === 'LocalSource') { diff --git a/src/app/channel-details/channel-details.module.ts b/src/app/channel-details/channel-details.module.ts index 19c7511..09f65a3 100644 --- a/src/app/channel-details/channel-details.module.ts +++ b/src/app/channel-details/channel-details.module.ts @@ -45,6 +45,9 @@ import { PacketDemodComponent } from './packet-demod/packet-demod.component'; import { PagerDemodComponent } from './pager-demod/pager-demod.component'; import { AisModComponent } from './ais-mod/ais-mod.component'; import { PacketModComponent } from './packet-mod/packet-mod.component'; +import { Ieee802154ModComponent } from './ieee802154-mod/ieee802154-mod.component'; +import { ChirpchatModComponent } from './chirpchat-mod/chirpchat-mod.component'; +import { ChirpchatDemodComponent } from './chirpchat-demod/chirpchat-demod.component'; @NgModule({ imports: [ @@ -95,7 +98,10 @@ import { PacketModComponent } from './packet-mod/packet-mod.component'; PacketDemodComponent, PagerDemodComponent, AisModComponent, - PacketModComponent + PacketModComponent, + Ieee802154ModComponent, + ChirpchatModComponent, + ChirpchatDemodComponent ] }) export class ChannelDetailsModule { } diff --git a/src/app/channel-details/channel-details.ts b/src/app/channel-details/channel-details.ts index cf8b8cc..66eadf6 100644 --- a/src/app/channel-details/channel-details.ts +++ b/src/app/channel-details/channel-details.ts @@ -29,6 +29,9 @@ import { PacketDemodReport, PacketDemodSettings } from './packet-demod/packet-de import { PagerDemodReport, PagerDemodSettings } from './pager-demod/pager-demod'; import { AISModActions, AISModReport, AISModSettings } from './ais-mod/ais-mod'; import { PacketModActions, PacketModReport, PacketModSettings } from './packet-mod/packet-mod'; +import { IEEE_802_15_4_ModActions, IEEE_802_15_4_ModReport, IEEE_802_15_4_ModSettings } from './ieee802154-mod/ieee802154-mod'; +import { ChirpChatModReport, ChirpChatModSettings } from './chirpchat-mod/chirpchat-mod'; +import { ChirpChatDemodReport, ChirpChatDemodSettings } from './chirpchat-demod/chirpchat-demod'; export interface ChannelSettings { channelType: string; @@ -38,11 +41,14 @@ export interface ChannelSettings { AISModSettings?: AISModSettings; AMDemodSettings?: AMDemodSettings; BFMDemodSettings?: BFMDemodSettings; + ChirpChatDemodSettings?: ChirpChatDemodSettings; + ChirpChatModSettings?: ChirpChatModSettings; FileSinkSettings?: FileSinkSettings; FileSourceSettings?: FileSourceSettings; FreeDVDemodSettings?: FreeDVDemodSettings; FreeDVModSettings?: FreeDVModSettings; FreqTrackerSettings?: FreqTrackerSettings; + IEEE_802_15_4_ModSettings?: IEEE_802_15_4_ModSettings; LocalSinkSettings?: LocalSinkSettings; LocalSourceSettings?: LocalSourceSettings; NFMDemodSettings?: NFMDemodSettings; @@ -71,11 +77,14 @@ export interface ChannelReport { AISModReport?: AISModReport; AMDemodReport?: AMDemodReport; BFMDemodReport?: BFMDemodReport; + ChirpChatDemodReport?: ChirpChatDemodReport; + ChirpChatModReport?: ChirpChatModReport; FileSinkReport?: FileSinkReport; FileSourceReport?: FileSourceReport; FreeDVDemodReport?: FreeDVDemodReport; FreeDVModReport?: FreeDVModReport; FreqTrackerReport?: FreqTrackerReport; + IEEE_802_15_4_ModReport?: IEEE_802_15_4_ModReport; NFMDemodReport?: NFMDemodReport; SSBDemodReport?: SSBDemodReport; DSDDemodReport?: DSDDemodReport; @@ -101,6 +110,7 @@ export interface ChannelActions { AISModActions?: AISModActions; FileSinkActions?: FileSinkActions; FileSourceActions?: FileSourceActions; + IEEE_802_15_4_ModActions?: IEEE_802_15_4_ModActions; PacketModActions?: PacketModActions; SigMFFileSinkActions?: SigMFFileSinkActions; channelType: string; diff --git a/src/app/channel-details/chirpchat-demod/chirpchat-demod.component.css b/src/app/channel-details/chirpchat-demod/chirpchat-demod.component.css new file mode 100644 index 0000000..787d2ce --- /dev/null +++ b/src/app/channel-details/chirpchat-demod/chirpchat-demod.component.css @@ -0,0 +1,114 @@ +.channel-card-header { + font-family: Arial, Helvetica, sans-serif; + font-size: 14px; + border: solid; + border-color: gray; + border-width: 1px; + margin-bottom: 5px; + padding: 0.2em 0.2em 0.2em 0.2em; +} + +.channel-header-rx { + background-color: rgb(170,255,200); +} + +.channel-header-comp { + margin-top: 1px; +} + +.button-card { + padding-top: 0px; + padding-right: 0px; + padding-bottom: 0px; + padding-left: 0px; + width: 22px; + height: 22px; +} + +.button-on { + border: solid; + border-width: 1px; + border-color: gray; + background-color: rgb(0, 255, 0, 1.0); +} + +.button-off { + border: solid; + border-width: 1px; + border-color: gray; + background-color: rgb(0, 0, 0, 0); +} + +.address-input { + width: 15ch; +} + +.volume-input { + width: 6ch; +} + +.squelch-input { + width: 6ch; +} + +.rfbw-input { + width: 6ch; +} + +.index-input { + width: 6ch; +} + +.file-input { + width: 70ch; +} + +.status-parity-ok { + background-color: rgb(134, 255, 110); +} + +.status-parity-error { + background-color: rgb(255, 134, 110); +} + +.status-parity-corrected { + background-color: rgb(110, 134, 255); +} + +.status-parity-undefined { + background-color: rgb(180, 180, 180); +} + +td { + background-color: rgb(230, 230, 210); +} + +.status-ok-card { + border: outset; + border-color: rgb(134, 255, 110); + border-width: 2px; + margin-bottom: 5px; + padding: 0.2em 0.2em 0.2em 0.2em; +} + +.status-ko-card { + border: solid; + border-color: rgb(255, 134, 110); + border-width: 2px; + padding: 0.2em 0.2em 0.2em 0.2em; +} + +::ng-deep .mat-card { + /* CSS styles go here */ + padding: 0px; /* for example to remove the margin */ +} + +::ng-deep .mat-checkbox-inner-container { + height: 14px!important; + width: 14px!important; + background-color: white; +} + +::ng-deep .mat-checkbox-label { + line-height: 16px!important; +} diff --git a/src/app/channel-details/chirpchat-demod/chirpchat-demod.component.html b/src/app/channel-details/chirpchat-demod/chirpchat-demod.component.html new file mode 100644 index 0000000..4ab5d2e --- /dev/null +++ b/src/app/channel-details/chirpchat-demod/chirpchat-demod.component.html @@ -0,0 +1,317 @@ + + +   +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ LoRa FEC: {{ this.report.nbParityBits }} - + COD: {{ this.report.nbCodewords }}/{{ this.report.nbSymbols }} - + PKT: {{ this.report.packetLength }} + + Header +   + + FEC + +   + + CRC + + + Payload +   + + FEC + +   + + CRC + +
+ Time: {{ this.report.messageTimestamp }} + + S: {{ this.report.signalPowerDB.toFixed(1) }} dB - N: {{ this.report.noisePowerDB.toFixed(1) }} dB - SNR: {{ this.report.snrPowerDB.toFixed(1) }} dB +
+ Msg: {{ this.report.messageString }} +
+ + + + Δf {{getDeltaFrequency()}} kHz +
+ BW + + + {{bandwidth.viewValue}} + + + + FFTW + + + {{fftWindow.viewValue}} + + + + + Scheme + + + {{codingScheme.viewValue}} + + +   + + +
+ SF + + + DE + + + Pre + +
+ + Decode + + + + Auto ML + + + + Header + +
+ + FEC + +   + + CRC + + + + EOM + + + Msg length + +
+ + Send via UDP + + + Addr + + + Port + +
+ + Reverse API + + + Device + + + Channel + +
+ Addr + + + Port + +
+
+
+ + {{ statusMessage }} + diff --git a/src/app/channel-details/chirpchat-demod/chirpchat-demod.component.spec.ts b/src/app/channel-details/chirpchat-demod/chirpchat-demod.component.spec.ts new file mode 100644 index 0000000..4448aeb --- /dev/null +++ b/src/app/channel-details/chirpchat-demod/chirpchat-demod.component.spec.ts @@ -0,0 +1,58 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatOptionModule } from '@angular/material/core'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatSelectModule } from '@angular/material/select'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { ActivatedRoute, RouterModule } from '@angular/router'; +import { ColorPickerModule } from 'ngx-color-picker'; +import { CommonComponentsModule } from 'src/app/common-components/common-components.module'; +import { ChannelHeaderComponent } from '../channel-header/channel-header.component'; +import { ChannelMonitorComponent } from '../channel-monitor/channel-monitor.component'; + +import { ChirpchatDemodComponent } from './chirpchat-demod.component'; + +describe('ChirpchatDemodComponent', () => { + let component: ChirpchatDemodComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + ChirpchatDemodComponent, + ChannelHeaderComponent, + ChannelMonitorComponent + ], + imports: [ + RouterModule, + FormsModule, + MatCardModule, + MatSelectModule, + MatOptionModule, + MatCheckboxModule, + MatTooltipModule, + MatProgressBarModule, + HttpClientTestingModule, + CommonComponentsModule, + ColorPickerModule + ], + providers: [ + {provide: ActivatedRoute, useValue: { snapshot: {parent: {params: {dix: 0, cix: 0}}}}} + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ChirpchatDemodComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/channel-details/chirpchat-demod/chirpchat-demod.component.ts b/src/app/channel-details/chirpchat-demod/chirpchat-demod.component.ts new file mode 100644 index 0000000..848701f --- /dev/null +++ b/src/app/channel-details/chirpchat-demod/chirpchat-demod.component.ts @@ -0,0 +1,471 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { interval, Subscription } from 'rxjs'; +import { Utils } from 'src/app/common-components/utils'; +import { DeviceStoreService } from 'src/app/device-store.service'; +import { DevicesetService } from 'src/app/deviceset/deviceset/deviceset.service'; +import { SdrangelUrlService } from 'src/app/sdrangel-url.service'; +import { ChannelSettings } from '../channel-details'; +import { ChannelDetailsService } from '../channel-details.service'; +import { ChirpChatDemodReport, ChirpChatDemodSettings, CHIRPCHATDEMOD_REPORT_DEFAULT, CHIRPCHATDEMOD_SETTINGS_DEFAULT } from './chirpchat-demod'; + +export interface Bandwidth { + index: number; + value: number; + viewValue: string; +} + +export interface FFTWindow { + value: number; + viewValue: string; +} + +export interface CodingScheme { + value: number; + viewValue: string; +} + +@Component({ + selector: 'app-chirpchat-demod', + templateUrl: './chirpchat-demod.component.html', + styleUrls: ['./chirpchat-demod.component.css'] +}) +export class ChirpchatDemodComponent implements OnInit { + deviceIndex: number; + channelIndex: number; + sdrangelURL: string; + settings: ChirpChatDemodSettings = CHIRPCHATDEMOD_SETTINGS_DEFAULT; + report: ChirpChatDemodReport = CHIRPCHATDEMOD_REPORT_DEFAULT; + deviceCenterFrequency: number; + deviceBasebandRate: number; + deviceStoreSubscription: Subscription; + channelReportSubscription: Subscription; + channelDeltaFrequency: number; + channelCenterFrequencyKhz: number; + channelMinFrequencyKhz: number; + channelMaxFrequencyKhz: number; + statusMessage: string; + statusError = false; + rgbTitle: number[] = [0, 0, 0]; + rgbTitleStr = 'rgb(0,0,0)'; + monitor: boolean; + bandwidths: Bandwidth[] = [ + {index: 0, value: 325, viewValue: '325'}, // 384k / 1024 + {index: 1, value: 750, viewValue: '750'}, // 384k / 512 + {index: 2, value: 1500, viewValue: '1500'}, // 384k / 256 + {index: 3, value: 2604, viewValue: '2604'}, // 333k / 128 + {index: 4, value: 3125, viewValue: '3125'}, // 400k / 128 + {index: 5, value: 3906, viewValue: '3906'}, // 500k / 128 + {index: 6, value: 5208, viewValue: '5208'}, // 333k / 64 + {index: 7, value: 6250, viewValue: '6250'}, // 400k / 64 + {index: 8, value: 7813, viewValue: '7813'}, // 500k / 64 + {index: 9, value: 10417, viewValue: '10417'}, // 333k / 32 + {index: 10, value: 12500, viewValue: '12.5k'}, // 400k / 32 + {index: 11, value: 15625, viewValue: '15625'}, // 500k / 32 + {index: 12, value: 20833, viewValue: '20833'}, // 333k / 16 + {index: 13, value: 25000, viewValue: '25k'}, // 400k / 16 + {index: 14, value: 31250, viewValue: '31250'}, // 500k / 16 + {index: 15, value: 41667, viewValue: '41667'}, // 333k / 8 + {index: 16, value: 50000, viewValue: '50k'}, // 400k / 8 + {index: 17, value: 62500, viewValue: '62.5k'}, // 500k / 8 + {index: 18, value: 83333, viewValue: '83.3k'}, // 333k / 4 + {index: 19, value: 100000, viewValue: '100k'}, // 400k / 4 + {index: 20, value: 125000, viewValue: '125k'}, // 500k / 4 + {index: 21, value: 166667, viewValue: '167k'}, // 333k / 2 + {index: 22, value: 200000, viewValue: '200k'}, // 400k / 2 + {index: 23, value: 250000, viewValue: '250k'}, // 500k / 2 + {index: 24, value: 333333, viewValue: '333k'}, // 333k / 1 + {index: 25, value: 400000, viewValue: '400k'}, // 400k / 1 + {index: 26, value: 500000, viewValue: '500k'} // 500k / 1 + ]; + fftWindows: FFTWindow[] = [ + {value: 0, viewValue: 'Bartlett'}, + {value: 1, viewValue: 'BmnHarris'}, + {value: 2, viewValue: 'Flattop'}, + {value: 3, viewValue: 'Hamming'}, + {value: 4, viewValue: 'Hanning'}, + {value: 5, viewValue: 'Rectangle'}, + {value: 6, viewValue: 'Kaiser'} + ]; + codingSchemes: CodingScheme[] = [ + {value: 0, viewValue: 'LoRa'}, + {value: 1, viewValue: 'ASCII'}, + {value: 2, viewValue: 'TTY'} + ]; + useReverseAPI: boolean; + sendViaUDP: boolean; + decodeActive: boolean; + autoNbSymbolsMax: boolean; + hasHeader: boolean; + eomSquelch: number; + hasCRC: boolean; + + constructor(private route: ActivatedRoute, + private channeldetailsService: ChannelDetailsService, + private deviceSetService: DevicesetService, + private sdrangelUrlService: SdrangelUrlService, + private deviceStoreService: DeviceStoreService) { + this.deviceStoreSubscription = null; + this.channelReportSubscription = null; + this.monitor = false; + this.sdrangelUrlService.currentUrlSource.subscribe(url => { + this.sdrangelURL = url; + }); + } + + ngOnInit(): void { + this.deviceIndex = +this.route.snapshot.parent.params['dix']; + this.channelIndex = +this.route.snapshot.parent.params['cix']; + this.getDeviceStorage(); + this.getChannelSettings(); + } + + getChannelSettings() { + this.channeldetailsService.getSettings(this.sdrangelURL, this.deviceIndex, this.channelIndex).subscribe( + channelSettings => { + if (channelSettings.channelType === 'ChirpChatDemod') { + this.statusMessage = 'OK'; + this.statusError = false; + this.settings = channelSettings.ChirpChatDemodSettings; + this.channelDeltaFrequency = this.settings.inputFrequencyOffset; + this.channelCenterFrequencyKhz = (this.deviceCenterFrequency + this.channelDeltaFrequency) / 1000; + this.channelMaxFrequencyKhz = (this.deviceCenterFrequency + (this.deviceBasebandRate / 2)) / 1000; + this.channelMinFrequencyKhz = (this.deviceCenterFrequency - (this.deviceBasebandRate / 2)) / 1000; + this.rgbTitle = Utils.intToRGB(this.settings.rgbColor); + this.rgbTitleStr = this.getRGBTitleStr(); + this.useReverseAPI = this.settings.useReverseAPI !== 0; + this.sendViaUDP = this.settings.sendViaUDP !== 0; + this.decodeActive = this.settings.decodeActive !== 0; + this.autoNbSymbolsMax = this.settings.autoNbSymbolsMax !== 0; + this.hasHeader = this.settings.hasHeader !== 0; + this.eomSquelch = this.settings.eomSquelchTenths / 10.0; + this.hasCRC = this.settings.hasCRC !== 0; + } else { + this.statusMessage = 'Not a ChirpChatDemod channel'; + this.statusError = true; + } + } + ); + } + + private getDeviceStorage() { + this.deviceStoreSubscription = this.deviceStoreService.get(this.deviceIndex).subscribe( + deviceStorage => { + this.deviceCenterFrequency = deviceStorage.centerFrequency; + this.deviceBasebandRate = deviceStorage.basebandRate; + }, + error => { + if (error === 'No device at this index') { + this.deviceSetService.getInfo(this.sdrangelURL, this.deviceIndex).subscribe( + deviceset => { + this.deviceStoreService.change( + this.deviceIndex, + { + basebandRate: deviceset.samplingDevice.bandwidth, + centerFrequency: deviceset.samplingDevice.centerFrequency + } + ); + this.deviceBasebandRate = deviceset.samplingDevice.bandwidth; + this.deviceCenterFrequency = deviceset.samplingDevice.centerFrequency; + } + ); + } + } + ); + } + + private setChannelSettings(chirpChatDemodSettings: ChirpChatDemodSettings) { + const settings: ChannelSettings = {}; + settings.channelType = 'ChirpChatDemod'; + settings.direction = 0, + settings.ChirpChatDemodSettings = chirpChatDemodSettings; + this.channeldetailsService.setSettings(this.sdrangelURL, this.deviceIndex, this.channelIndex, settings).subscribe( + res => { + console.log('Set settings OK', res); + this.statusMessage = 'OK'; + this.statusError = false; + this.getChannelSettings(); + }, + error => { + this.statusMessage = error.message; + this.statusError = true; + } + ); + } + + enableReporting(enable: boolean) { + if (enable) { + this.channelReportSubscription = interval(1000).subscribe( + _ => { + this.channeldetailsService.getReport(this.sdrangelURL, this.deviceIndex, this.channelIndex).subscribe( + channelReport => { + if (channelReport.channelType === 'ChirpChatDemod') { + this.report = channelReport.ChirpChatDemodReport; + } + } + ); + } + ); + } else { + this.channelReportSubscription.unsubscribe(); + this.channelReportSubscription = null; + } + } + + toggleMonitor() { + this.monitor = !this.monitor; + this.enableReporting(this.monitor); + } + + onFrequencyUpdate(frequency: number) { + this.channelCenterFrequencyKhz = frequency; + this.setCenterFrequency(); + } + + setCenterFrequency() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.inputFrequencyOffset = this.channelCenterFrequencyKhz * 1000 - this.deviceCenterFrequency; + this.setChannelSettings(newSettings); + } + + getDeltaFrequency(): number { + const frequency = this.channelCenterFrequencyKhz - (this.deviceCenterFrequency / 1000); + return +frequency.toFixed(3); + } + + getRGBTitleStr(): string { + return 'rgb(' + this.rgbTitle[0].toString() + ',' + this.rgbTitle[1].toString() + ',' + this.rgbTitle[2].toString() + ')'; + } + + onTitleColorChanged(colorStr: string) { + this.rgbTitleStr = colorStr; + this.setTitleColor(); + } + + setTitleColor() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.rgbColor = Utils.rgbToInt(this.rgbTitleStr); + this.setChannelSettings(newSettings); + } + + onTitleChanged(title: string) { + this.settings.title = title; + this.setTitle(); + } + + setTitle() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.title = this.settings.title; + this.setChannelSettings(newSettings); + } + + setSendViaUDP() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.sendViaUDP = this.sendViaUDP ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setUDpAddress() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.udpAddress = this.settings.udpAddress; + this.setChannelSettings(newSettings); + } + + setUdpPort() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.udpPort = this.settings.udpPort; + this.setChannelSettings(newSettings); + } + + setUseReverseAPI() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.useReverseAPI = this.useReverseAPI ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setReverseAPIAddress() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.reverseAPIAddress = this.settings.reverseAPIAddress; + this.setChannelSettings(newSettings); + } + + setReverseAPIPort() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.reverseAPIPort = this.settings.reverseAPIPort; + this.setChannelSettings(newSettings); + } + + setReverseAPIDeviceIndex() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.reverseAPIDeviceIndex = this.settings.reverseAPIDeviceIndex; + this.setChannelSettings(newSettings); + } + + setReverseAPIChannelIndex() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.reverseAPIChannelIndex = this.settings.reverseAPIChannelIndex; + this.setChannelSettings(newSettings); + } + + setNbSymbolsMax() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.nbSymbolsMax = this.settings.nbSymbolsMax; + this.setChannelSettings(newSettings); + } + + setEOMSquelch() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.eomSquelchTenths = this.eomSquelch * 10.0; + this.setChannelSettings(newSettings); + } + + setHasCRC() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.hasCRC = this.hasCRC ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setNbParityBits() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.nbParityBits = this.settings.nbParityBits < 0 ? 0 : this.settings.nbParityBits > 4 ? 4 : this.settings.nbParityBits; + this.setChannelSettings(newSettings); + } + + setHasHeader() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.hasHeader = this.hasHeader ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setAutoNbSymbolsMax() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.autoNbSymbolsMax = this.autoNbSymbolsMax ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setDecodeActive() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.decodeActive = this.decodeActive ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setPreambleChirps() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.preambleChirps = this.settings.preambleChirps < 4 ? 4 : this.settings.preambleChirps > 20 ? 20 : this.settings.preambleChirps; + this.setChannelSettings(newSettings); + } + + setDEBits() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.deBits = this.settings.deBits < 0 ? 0 : this.settings.deBits > 4 ? 4 : this.settings.deBits; + this.setChannelSettings(newSettings); + } + + setSpreadFactor() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.spreadFactor = this.settings.spreadFactor < 7 ? 7 : this.settings.spreadFactor > 12 ? 12 : this.settings.spreadFactor; + this.setChannelSettings(newSettings); + } + + setCodingScheme() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.codingScheme = this.settings.codingScheme; + this.setChannelSettings(newSettings); + } + + setFFTWindow() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.fftWindow = this.settings.fftWindow; + this.setChannelSettings(newSettings); + } + + setBandwidth() { + const newSettings: ChirpChatDemodSettings = {}; + newSettings.bandwidthIndex = this.settings.bandwidthIndex; + this.setChannelSettings(newSettings); + } + + getHdrFECColor() { + if (this.hasHeader && (this.report.headerParityStatus === 3)) { // OK + return 'green'; + } else if (this.hasHeader && (this.report.headerParityStatus === 1)) { // Error + return 'red'; + } else if (this.hasHeader && (this.report.headerParityStatus === 2)) { // Corrected + return 'blue'; + } else { + return 'grey'; + } + } + + getHdrFECText() { + if (this.hasHeader && (this.report.headerParityStatus === 3)) { // OK + return 'Header parity OK'; + } else if (this.hasHeader && (this.report.headerParityStatus === 1)) { // Error + return 'Header parity error'; + } else if (this.hasHeader && (this.report.headerParityStatus === 2)) { // Corrected + return 'Header parity corrected'; + } else { + return 'Header parity undefined'; + } + } + + getHdrCRCColor() { + if (this.hasHeader && (this.report.headerCRCStatus !== 0)) { + return 'green'; + } else if (this.hasHeader && (this.report.headerCRCStatus === 0)) { + return 'red'; + } else { + return 'grey'; + } + } + + getHdrCRCText() { + if (this.hasHeader && (this.report.headerCRCStatus !== 0)) { + return 'Header CRC OK'; + } else if (this.hasHeader && (this.report.headerCRCStatus === 0)) { + return 'Header CRC error'; + } else { + return 'Header CRC undefined'; + } + } + + getPayFECColor() { + if (this.report.payloadParityStatus === 3) { // OK + return 'green'; + } else if (this.report.payloadParityStatus === 1) { // Error + return 'red'; + } else if (this.report.payloadParityStatus === 2) { // Corrected + return 'blue'; + } else { + return 'grey'; + } + } + + getPayFECText() { + if (this.report.payloadParityStatus === 3) { // OK + return 'Payload parity OK'; + } else if (this.report.payloadParityStatus === 1) { // Error + return 'Payload parity error'; + } else if (this.report.payloadParityStatus === 2) { // Corrected + return 'Payload parity corrected'; + } else { + return 'Payload parity undefined'; + } + } + + getPayCRCColor() { + if (this.report.payloadCRCStatus !== 0) { + return 'green'; + } else if (this.report.payloadCRCStatus === 0) { + return 'red'; + } + } + + getPayCRCText() { + if (this.report.payloadCRCStatus !== 0) { + return 'Payload CRC OK'; + } else if (this.report.payloadCRCStatus === 0) { + return 'Payload CRC error'; + } + } +} diff --git a/src/app/channel-details/chirpchat-demod/chirpchat-demod.ts b/src/app/channel-details/chirpchat-demod/chirpchat-demod.ts new file mode 100644 index 0000000..dab2f23 --- /dev/null +++ b/src/app/channel-details/chirpchat-demod/chirpchat-demod.ts @@ -0,0 +1,93 @@ +export interface ChirpChatDemodSettings { + autoNbSymbolsMax: number; // boolean + bandwidthIndex: number; + codingScheme: number; + deBits: number; + decodeActive: number; // boolean + eomSquelchTenths: number; + fftWindow: number; + hasCRC: number; // boolean + hasHeader: number; // boolean + inputFrequencyOffset: number; + nbParityBits: number; + nbSymbolsMax: number; + preambleChirps: number; + reverseAPIAddress: string; + reverseAPIChannelIndex: number; + reverseAPIDeviceIndex: number; + reverseAPIPort: number; + rgbColor: number; + sendViaUDP: number; // boolean + spreadFactor: number; + title: string; + udpAddress: string; + udpPort: number; + useReverseAPI: number; // booleab +} + +export const CHIRPCHATDEMOD_SETTINGS_DEFAULT = { + autoNbSymbolsMax: 0, + bandwidthIndex: 1, + codingScheme: 2, + deBits: 2, + decodeActive: 1, + eomSquelchTenths: 68, + fftWindow: 5, + hasCRC: 1, + hasHeader: 0, + inputFrequencyOffset: 0, + nbParityBits: 2, + nbSymbolsMax: 140, + preambleChirps: 8, + reverseAPIAddress: '127.0.0.1', + reverseAPIChannelIndex: 0, + reverseAPIDeviceIndex: 0, + reverseAPIPort: 8888, + rgbColor: -65281, + sendViaUDP: 0, + spreadFactor: 7, + title: 'ChirpChat Demodulator', + udpAddress: '127.0.0.1', + udpPort: 9999, + useReverseAPI: 0 +}; + +export interface ChirpChatDemodReport { + channelPowerDB: number; + channelSampleRate: number; + hasCRC: number; // boolean + headerCRCStatus: number; // boolean + headerParityStatus: number; + messageString: string; + messageTimestamp: string; + nbCodewords: number; + nbParityBits: number; + nbSymbols: number; + noisePowerDB: number; + packetLength: number; + payloadCRCStatus: number; + payloadParityStatus: number; + signalPowerDB: number; + snrPowerDB: number; + decoding: number; // boolean +} + +export const CHIRPCHATDEMOD_REPORT_DEFAULT = { + channelPowerDB: -66.6503677368164, + channelSampleRate: 3000, + hasCRC: 0, + headerCRCStatus: 1, + headerParityStatus: 0, + messageString: 'VVV DE F4EXB JN33NN', + messageTimestamp: '2021-11-21T23:01:33.598', + nbCodewords: 110, + nbParityBits: 2, + nbSymbols: 134, + noisePowerDB: -59.88705825805664, + packetLength: 55, + payloadCRCStatus: 1, + payloadParityStatus: 0, + signalPowerDB: -46.79887390136719, + snrPowerDB: 12.644622802734375, + decoding: 0 +}; diff --git a/src/app/channel-details/chirpchat-mod/chirpchat-mod.component.css b/src/app/channel-details/chirpchat-mod/chirpchat-mod.component.css new file mode 100644 index 0000000..ea86127 --- /dev/null +++ b/src/app/channel-details/chirpchat-mod/chirpchat-mod.component.css @@ -0,0 +1,107 @@ +.channel-card-header { + font-family: Arial, Helvetica, sans-serif; + font-size: 14px; + border: solid; + border-color: gray; + border-width: 1px; + margin-bottom: 5px; + padding: 0.2em 0.2em 0.2em 0.2em; +} + +.channel-header-tx { + background-color: rgb(255,200,170); +} + +.channel-header-comp { + margin-top: 1px; +} + +.button-card { + padding-top: 0px; + padding-right: 0px; + padding-bottom: 0px; + padding-left: 0px; + width: 22px; + height: 22px; +} + +.button-on { + border: solid; + border-width: 1px; + border-color: gray; + background-color: rgb(0, 255, 0, 1.0); +} + +.button-off { + border: solid; + border-width: 1px; + border-color: gray; + background-color: rgb(0, 0, 0, 0); +} + +.sync-input { + width: 4ch; +} + +.squelch-input { + width: 6ch; +} + +.rfbw-input { + width: 6ch; +} + +.index-input { + width: 6ch; +} + +.call-input { + width: 10ch; +} + +.data-input { + width: 80ch; +} + +.latlon-input { + width: 12ch; +} + +.source-input { + width: 10ch; + background-color: white; +} + +td { + background-color: rgb(230, 230, 210); +} + +.status-ok-card { + border: outset; + border-color: rgb(134, 255, 110); + border-width: 2px; + margin-bottom: 5px; + padding: 0.2em 0.2em 0.2em 0.2em; +} + +.status-ko-card { + border: solid; + border-color: rgb(255, 134, 110); + border-width: 2px; + padding: 0.2em 0.2em 0.2em 0.2em; +} + +::ng-deep .mat-card { + /* CSS styles go here */ + padding: 0px; /* for example to remove the margin */ +} + +::ng-deep .mat-checkbox-inner-container { + height: 14px!important; + width: 14px!important; + background-color: white; +} + +::ng-deep .mat-checkbox-label { + line-height: 16px!important; +} diff --git a/src/app/channel-details/chirpchat-mod/chirpchat-mod.component.html b/src/app/channel-details/chirpchat-mod/chirpchat-mod.component.html new file mode 100644 index 0000000..97937e4 --- /dev/null +++ b/src/app/channel-details/chirpchat-mod/chirpchat-mod.component.html @@ -0,0 +1,328 @@ + + +   +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + Repeat + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ Tsym {{ report.symbolTimeMs.toFixed(1) }} ms + + Tpay {{ report.payloadTimeMs.toFixed(1) }} ms + + Ttot {{ report.totalTimeMs.toFixed(1) }} ms +
+ + + + Δf {{getDeltaFrequency()}} kHz +
+ BW + + + {{bandwidth.viewValue}} + + + + SF + + + DE + +
+ + Scheme + + + {{codingScheme.viewValue}} + + +   + + + + Pre + + + Idle + + s +
+ Msg + + + {{messageType.viewValue}} + + + + + FEC + +   + + CRC + +   + + HDR + + + + Sync + + 0x{{ settings.syncWord.toString(16) }} +
+ MyCall + + + UrCall + +
+ MyRpt + + + MyLoc + +
+ Text + +
+ Msg: {{ getCurrentMessage() }} +
+ + + + + + Mute + + + +
+ + Bytes from UDP + + + Addr + + + Port + +
+ + Reverse API + + + Device + + + Channel + +
+ Addr + + + Port + +
+ + + + +
+
+
+ + {{ statusMessage }} + diff --git a/src/app/channel-details/chirpchat-mod/chirpchat-mod.component.spec.ts b/src/app/channel-details/chirpchat-mod/chirpchat-mod.component.spec.ts new file mode 100644 index 0000000..fe3120c --- /dev/null +++ b/src/app/channel-details/chirpchat-mod/chirpchat-mod.component.spec.ts @@ -0,0 +1,57 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatOptionModule } from '@angular/material/core'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatSelectModule } from '@angular/material/select'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { ActivatedRoute, RouterModule } from '@angular/router'; +import { ColorPickerModule } from 'ngx-color-picker'; +import { CommonComponentsModule } from 'src/app/common-components/common-components.module'; +import { ChannelHeaderComponent } from '../channel-header/channel-header.component'; +import { ChannelMonitorComponent } from '../channel-monitor/channel-monitor.component'; +import { ChirpchatModComponent } from './chirpchat-mod.component'; + +describe('ChirpchatModComponent', () => { + let component: ChirpchatModComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + ChirpchatModComponent, + ChannelHeaderComponent, + ChannelMonitorComponent + ], + imports: [ + RouterModule, + FormsModule, + MatCardModule, + MatSelectModule, + MatOptionModule, + MatCheckboxModule, + MatTooltipModule, + MatProgressBarModule, + HttpClientTestingModule, + CommonComponentsModule, + ColorPickerModule + ], + providers: [ + {provide: ActivatedRoute, useValue: { snapshot: {parent: {params: {dix: 0, cix: 0}}}}} + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ChirpchatModComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/channel-details/chirpchat-mod/chirpchat-mod.component.ts b/src/app/channel-details/chirpchat-mod/chirpchat-mod.component.ts new file mode 100644 index 0000000..942793d --- /dev/null +++ b/src/app/channel-details/chirpchat-mod/chirpchat-mod.component.ts @@ -0,0 +1,470 @@ +import { N } from '@angular/cdk/keycodes'; +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { interval, Subscription } from 'rxjs'; +import { Utils } from 'src/app/common-components/utils'; +import { DeviceStoreService } from 'src/app/device-store.service'; +import { DevicesetService } from 'src/app/deviceset/deviceset/deviceset.service'; +import { SdrangelUrlService } from 'src/app/sdrangel-url.service'; +import { ChannelSettings } from '../channel-details'; +import { ChannelDetailsService } from '../channel-details.service'; +import { ChirpChatModReport, ChirpChatModSettings, CHIRPCHATMOD_REPORT_DEFAULT, CHIRPCHATMOD_SETTINGS_DEFAULT } from './chirpchat-mod'; + +export interface Bandwidth { + index: number; + value: number; + viewValue: string; +} + +export interface CodingScheme { + value: number; + viewValue: string; +} + +export interface MessageType { + value: number; + viewValue: string; +} + +@Component({ + selector: 'app-chirpchat-mod', + templateUrl: './chirpchat-mod.component.html', + styleUrls: ['./chirpchat-mod.component.css'] +}) +export class ChirpchatModComponent implements OnInit { + deviceIndex: number; + channelIndex: number; + sdrangelURL: string; + settings: ChirpChatModSettings = CHIRPCHATMOD_SETTINGS_DEFAULT; + report: ChirpChatModReport = CHIRPCHATMOD_REPORT_DEFAULT; + deviceCenterFrequency: number; + deviceBasebandRate: number; + deviceStoreSubscription: Subscription; + channelReportSubscription: Subscription; + channelDeltaFrequency: number; + channelCenterFrequencyKhz: number; + channelMinFrequencyKhz: number; + channelMaxFrequencyKhz: number; + statusMessage: string; + statusError = false; + rgbTitle: number[] = [0, 0, 0]; + rgbTitleStr = 'rgb(0,0,0)'; + monitor: boolean; + bandwidths: Bandwidth[] = [ + {index: 0, value: 325, viewValue: '325'}, // 384k / 1024 + {index: 1, value: 750, viewValue: '750'}, // 384k / 512 + {index: 2, value: 1500, viewValue: '1500'}, // 384k / 256 + {index: 3, value: 2604, viewValue: '2604'}, // 333k / 128 + {index: 4, value: 3125, viewValue: '3125'}, // 400k / 128 + {index: 5, value: 3906, viewValue: '3906'}, // 500k / 128 + {index: 6, value: 5208, viewValue: '5208'}, // 333k / 64 + {index: 7, value: 6250, viewValue: '6250'}, // 400k / 64 + {index: 8, value: 7813, viewValue: '7813'}, // 500k / 64 + {index: 9, value: 10417, viewValue: '10417'}, // 333k / 32 + {index: 10, value: 12500, viewValue: '12.5k'}, // 400k / 32 + {index: 11, value: 15625, viewValue: '15625'}, // 500k / 32 + {index: 12, value: 20833, viewValue: '20833'}, // 333k / 16 + {index: 13, value: 25000, viewValue: '25k'}, // 400k / 16 + {index: 14, value: 31250, viewValue: '31250'}, // 500k / 16 + {index: 15, value: 41667, viewValue: '41667'}, // 333k / 8 + {index: 16, value: 50000, viewValue: '50k'}, // 400k / 8 + {index: 17, value: 62500, viewValue: '62.5k'}, // 500k / 8 + {index: 18, value: 83333, viewValue: '83.3k'}, // 333k / 4 + {index: 19, value: 100000, viewValue: '100k'}, // 400k / 4 + {index: 20, value: 125000, viewValue: '125k'}, // 500k / 4 + {index: 21, value: 166667, viewValue: '167k'}, // 333k / 2 + {index: 22, value: 200000, viewValue: '200k'}, // 400k / 2 + {index: 23, value: 250000, viewValue: '250k'}, // 500k / 2 + {index: 24, value: 333333, viewValue: '333k'}, // 333k / 1 + {index: 25, value: 400000, viewValue: '400k'}, // 400k / 1 + {index: 26, value: 500000, viewValue: '500k'} // 500k / 1 + ]; + codingSchemes: CodingScheme[] = [ + {value: 0, viewValue: 'LoRa'}, + {value: 1, viewValue: 'ASCII'}, + {value: 2, viewValue: 'TTY'} + ]; + messageTypes: MessageType[] = [ + {value: 0, viewValue: 'None'}, + {value: 1, viewValue: 'Beacon'}, + {value: 2, viewValue: 'CQ'}, + {value: 3, viewValue: 'Reply'}, + {value: 4, viewValue: 'Report'}, + {value: 5, viewValue: 'RReport'}, + {value: 6, viewValue: 'RRR'}, + {value: 7, viewValue: '73'}, + {value: 8, viewValue: 'QSOTxt'}, + {value: 9, viewValue: 'Text'}, + {value: 10, viewValue: 'Bytes'} + ]; + channelMute: boolean; + hasCRC: boolean; + hasHeader: boolean; + udpEnabled: boolean; + useReverseAPI: boolean; + quietSeconds: number; + + constructor(private route: ActivatedRoute, + private channeldetailsService: ChannelDetailsService, + private deviceSetService: DevicesetService, + private sdrangelUrlService: SdrangelUrlService, + private deviceStoreService: DeviceStoreService) { + this.deviceStoreSubscription = null; + this.channelReportSubscription = null; + this.monitor = false; + this.sdrangelUrlService.currentUrlSource.subscribe(url => { + this.sdrangelURL = url; + }); + } + + ngOnInit(): void { + this.deviceIndex = +this.route.snapshot.parent.params['dix']; + this.channelIndex = +this.route.snapshot.parent.params['cix']; + this.getDeviceStorage(); + this.getChannelSettings(); + } + + getChannelSettings() { + this.channeldetailsService.getSettings(this.sdrangelURL, this.deviceIndex, this.channelIndex).subscribe( + channelSettings => { + if (channelSettings.channelType === 'ChirpChatMod') { + this.statusMessage = 'OK'; + this.statusError = false; + this.settings = channelSettings.ChirpChatModSettings; + this.channelDeltaFrequency = this.settings.inputFrequencyOffset; + this.channelCenterFrequencyKhz = (this.deviceCenterFrequency + this.channelDeltaFrequency) / 1000; + this.channelMaxFrequencyKhz = (this.deviceCenterFrequency + (this.deviceBasebandRate / 2)) / 1000; + this.channelMinFrequencyKhz = (this.deviceCenterFrequency - (this.deviceBasebandRate / 2)) / 1000; + this.rgbTitle = Utils.intToRGB(this.settings.rgbColor); + this.rgbTitleStr = this.getRGBTitleStr(); + this.channelMute = this.settings.channelMute !== 0; + this.hasCRC = this.settings.hasCRC !== 0; + this.hasHeader = this.settings.hasHeader !== 0; + this.udpEnabled = this.settings.udpEnabled !== 0; + this.useReverseAPI = this.settings.useReverseAPI !== 0; + this.quietSeconds = this.settings.quietMillis / 1000.0; + } else { + this.statusMessage = 'Not a ChirpChatMod channel'; + this.statusError = true; + } + } + ); + } + + getRGBTitleStr(): string { + return 'rgb(' + this.rgbTitle[0].toString() + ',' + this.rgbTitle[1].toString() + ',' + this.rgbTitle[2].toString() + ')'; + } + + private getDeviceStorage() { + this.deviceStoreSubscription = this.deviceStoreService.get(this.deviceIndex).subscribe( + deviceStorage => { + this.deviceCenterFrequency = deviceStorage.centerFrequency; + this.deviceBasebandRate = deviceStorage.basebandRate; + }, + error => { + if (error === 'No device at this index') { + this.deviceSetService.getInfo(this.sdrangelURL, this.deviceIndex).subscribe( + deviceset => { + this.deviceStoreService.change( + this.deviceIndex, + { + basebandRate: deviceset.samplingDevice.bandwidth, + centerFrequency: deviceset.samplingDevice.centerFrequency + } + ); + this.deviceBasebandRate = deviceset.samplingDevice.bandwidth; + this.deviceCenterFrequency = deviceset.samplingDevice.centerFrequency; + } + ); + } + } + ); + } + + private setChannelSettings(chirpChatModSettings: ChirpChatModSettings) { + const settings: ChannelSettings = {}; + settings.channelType = 'ChirpChatMod'; + settings.direction = 1, + settings.ChirpChatModSettings = chirpChatModSettings; + this.channeldetailsService.setSettings(this.sdrangelURL, this.deviceIndex, this.channelIndex, settings).subscribe( + res => { + console.log('Set settings OK', res); + this.statusMessage = 'OK'; + this.statusError = false; + this.getChannelSettings(); + }, + error => { + this.statusMessage = error.message; + this.statusError = true; + } + ); + } + + enableReporting(enable: boolean) { + if (enable) { + this.channelReportSubscription = interval(1000).subscribe( + _ => { + this.channeldetailsService.getReport(this.sdrangelURL, this.deviceIndex, this.channelIndex).subscribe( + channelReport => { + if (channelReport.channelType === 'ChirpChatMod') { + this.report = channelReport.ChirpChatModReport; + } + } + ); + } + ); + } else { + this.channelReportSubscription.unsubscribe(); + this.channelReportSubscription = null; + } + } + + toggleMonitor() { + this.monitor = !this.monitor; + this.enableReporting(this.monitor); + } + + onFrequencyUpdate(frequency: number) { + this.channelCenterFrequencyKhz = frequency; + this.setCenterFrequency(); + } + + setCenterFrequency() { + const newSettings: ChirpChatModSettings = {}; + newSettings.inputFrequencyOffset = this.channelCenterFrequencyKhz * 1000 - this.deviceCenterFrequency; + this.setChannelSettings(newSettings); + } + + getDeltaFrequency(): number { + const frequency = this.channelCenterFrequencyKhz - (this.deviceCenterFrequency / 1000); + return +frequency.toFixed(3); + } + + onTitleColorChanged(colorStr: string) { + this.rgbTitleStr = colorStr; + this.setTitleColor(); + } + + setTitleColor() { + const newSettings: ChirpChatModSettings = {}; + newSettings.rgbColor = Utils.rgbToInt(this.rgbTitleStr); + this.setChannelSettings(newSettings); + } + + onTitleChanged(title: string) { + this.settings.title = title; + this.setTitle(); + } + + setTitle() { + const newSettings: ChirpChatModSettings = {}; + newSettings.title = this.settings.title; + this.setChannelSettings(newSettings); + } + + setUdpEnabled() { + const newSettings: ChirpChatModSettings = {}; + newSettings.udpEnabled = this.udpEnabled ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setUDpAddress() { + const newSettings: ChirpChatModSettings = {}; + newSettings.udpAddress = this.settings.udpAddress; + this.setChannelSettings(newSettings); + } + + setUdpPort() { + const newSettings: ChirpChatModSettings = {}; + newSettings.udpPort = this.settings.udpPort; + this.setChannelSettings(newSettings); + } + + setUseReverseAPI() { + const newSettings: ChirpChatModSettings = {}; + newSettings.useReverseAPI = this.useReverseAPI ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setReverseAPIAddress() { + const newSettings: ChirpChatModSettings = {}; + newSettings.reverseAPIAddress = this.settings.reverseAPIAddress; + this.setChannelSettings(newSettings); + } + + setReverseAPIPort() { + const newSettings: ChirpChatModSettings = {}; + newSettings.reverseAPIPort = this.settings.reverseAPIPort; + this.setChannelSettings(newSettings); + } + + setReverseAPIDeviceIndex() { + const newSettings: ChirpChatModSettings = {}; + newSettings.reverseAPIDeviceIndex = this.settings.reverseAPIDeviceIndex; + this.setChannelSettings(newSettings); + } + + setReverseAPIChannelIndex() { + const newSettings: ChirpChatModSettings = {}; + newSettings.reverseAPIChannelIndex = this.settings.reverseAPIChannelIndex; + this.setChannelSettings(newSettings); + } + + setChannelMute() { + const newSettings: ChirpChatModSettings = {}; + newSettings.channelMute = this.channelMute ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setMessageRepeat() { + const newSettings: ChirpChatModSettings = {}; + newSettings.messageRepeat = this.settings.messageRepeat; + this.setChannelSettings(newSettings); + } + + setTextMessage() { + const newSettings: ChirpChatModSettings = {}; + newSettings.textMessage = this.settings.textMessage; + this.setChannelSettings(newSettings); + } + + setMyLoc() { + const newSettings: ChirpChatModSettings = {}; + newSettings.myLoc = this.settings.myLoc; + this.setChannelSettings(newSettings); + } + + setMyRpt() { + const newSettings: ChirpChatModSettings = {}; + newSettings.myRpt = this.settings.myRpt; + this.setChannelSettings(newSettings); + } + + setUrCall() { + const newSettings: ChirpChatModSettings = {}; + newSettings.urCall = this.settings.urCall; + this.setChannelSettings(newSettings); + } + + setMyCall() { + const newSettings: ChirpChatModSettings = {}; + newSettings.myCall = this.settings.myCall; + this.setChannelSettings(newSettings); + } + + setSyncWord() { + const newSettings: ChirpChatModSettings = {}; + newSettings.syncWord = this.settings.syncWord < 0 ? 0 : this.settings.syncWord > 255 ? 255 : this.settings.syncWord; + this.setChannelSettings(newSettings); + } + + setHasHeader() { + const newSettings: ChirpChatModSettings = {}; + newSettings.hasHeader = this.hasHeader ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setHasCRC() { + const newSettings: ChirpChatModSettings = {}; + newSettings.hasCRC = this.hasCRC ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setNbParityBits() { + const newSettings: ChirpChatModSettings = {}; + newSettings.nbParityBits = this.settings.nbParityBits < 0 ? 0 : this.settings.nbParityBits > 4 ? 4 : this.settings.nbParityBits; + this.setChannelSettings(newSettings); + } + + setPreambleChirps() { + const newSettings: ChirpChatModSettings = {}; + newSettings.preambleChirps = this.settings.preambleChirps < 4 ? 4 : this.settings.preambleChirps > 20 ? 20 : this.settings.preambleChirps; + this.setChannelSettings(newSettings); + } + + setQuietSeconds() { + const newSettings: ChirpChatModSettings = {}; + newSettings.quietMillis = this.quietSeconds < 0.1 ? 100 : this.quietSeconds > 90 ? 90000 : this.quietSeconds * 1000.0; + this.setChannelSettings(newSettings); + } + + setCodingScheme() { + const newSettings: ChirpChatModSettings = {}; + newSettings.codingScheme = this.settings.codingScheme; + this.setChannelSettings(newSettings); + } + + setDEBits() { + const newSettings: ChirpChatModSettings = {}; + newSettings.deBits = this.settings.deBits < 0 ? 0 : this.settings.deBits > 4 ? 4 : this.settings.deBits; + this.setChannelSettings(newSettings); + } + + setSpreadFactor() { + const newSettings: ChirpChatModSettings = {}; + newSettings.spreadFactor = this.settings.spreadFactor < 7 ? 7 : this.settings.spreadFactor > 12 ? 12 : this.settings.spreadFactor; + this.setChannelSettings(newSettings); + } + + setBandwidth() { + const newSettings: ChirpChatModSettings = {}; + newSettings.bandwidthIndex = this.settings.bandwidthIndex; + this.setChannelSettings(newSettings); + } + + setMessageType() { + const newSettings: ChirpChatModSettings = {}; + newSettings.messageType = this.settings.messageType; + this.setChannelSettings(newSettings); + } + + getCurrentMessage(): string { + if (this.settings.messageType === 0) { // None + return ''; + } else if (this.settings.messageType === 1) { // Beacon + return this.settings.beaconMessage; + } else if (this.settings.messageType === 2) { // CQ + return this.settings.cqMessage; + } else if (this.settings.messageType === 3) { // Reply + return this.settings.replyMessage; + } else if (this.settings.messageType === 4) { // Report + return this.settings.reportMessage; + } else if (this.settings.messageType === 5) { // Reply report + return this.settings.replyReportMessage; + } else if (this.settings.messageType === 6) { // RRR + return this.settings.rrrMessage; + } else if (this.settings.messageType === 7) { // 73 + return this.settings.message73; + } else if (this.settings.messageType === 8) { // QSO text + return this.settings.qsoTextMessage; + } else if (this.settings.messageType === 9) { // Text + return this.settings.textMessage; + } else if (this.settings.messageType === 10) { // Bytes + return ''; + } else { + return ''; + } + } + + generateMessages() { + const newSettings: ChirpChatModSettings = {}; + newSettings.beaconMessage = 'VVV DE ' + this.settings.myCall + ' ' + this.settings.myLoc; + newSettings.cqMessage = 'CQ DE ' + this.settings.myCall + ' ' + this.settings.myLoc; + newSettings.replyMessage = this.settings.urCall + ' ' + this.settings.myCall + ' ' + this.settings.myLoc; + newSettings.reportMessage = this.settings.urCall + ' ' + this.settings.myCall + ' ' + this.settings.myRpt; + newSettings.replyReportMessage = this.settings.urCall + ' ' + this.settings.myCall + ' R' + this.settings.myRpt; + newSettings.rrrMessage = this.settings.urCall + ' ' + this.settings.myCall + ' RRR'; + newSettings.message73 = this.settings.urCall + ' ' + this.settings.myCall + ' 73'; + newSettings.qsoTextMessage = this.settings.urCall + ' ' + this.settings.myCall + ' ' + this.settings.textMessage; + this.setChannelSettings(newSettings); + } + + tx() { + const newSettings: ChirpChatModSettings = {}; + const currentMsgType = this.settings.messageType; + newSettings.messageType = 0; // cycle through None + this.setChannelSettings(newSettings); + newSettings.messageType = currentMsgType; + this.setChannelSettings(newSettings); + } +} diff --git a/src/app/channel-details/chirpchat-mod/chirpchat-mod.ts b/src/app/channel-details/chirpchat-mod/chirpchat-mod.ts new file mode 100644 index 0000000..c742023 --- /dev/null +++ b/src/app/channel-details/chirpchat-mod/chirpchat-mod.ts @@ -0,0 +1,95 @@ +export interface ChirpChatModSettings { + bandwidthIndex: number; // Index in ChirpChatModSettings::bandwidths array + beaconMessage: string; + channelMute: number; // boolean + codingScheme: number; // ChirpChatModSettings::CodingScheme + cqMessage: string; + deBits: number; + hasCRC: number; // boolean + hasHeader: number; // boolean + inputFrequencyOffset: number; + message73: string; + messageRepeat: number; // 0 for infinite + messageType: number; // ChirpChatModSettings::MessageType + myCall: string; + myLoc: string; + myRpt: string; + nbParityBits: number; + preambleChirps: number; + qsoTextMessage: string; + quietMillis: number; + replyMessage: string; + replyReportMessage: string; + reportMessage: string; + reverseAPIAddress: string; + reverseAPIChannelIndex: number; + reverseAPIDeviceIndex: number; + reverseAPIPort: number; + rgbColor: number; + rrrMessage: string; + spreadFactor: number; + syncWord: number; + textMessage: string; + title: string; + udpAddress: string; + udpEnabled: number; // boolean + udpPort: number; + urCall: string; + useReverseAPI: number; // boolean +} + +export const CHIRPCHATMOD_SETTINGS_DEFAULT = { + bandwidthIndex: 5, + beaconMessage: 'VVV DE %1 %2', + channelMute: 0, + codingScheme: 0, + cqMessage: 'CQ DE %1 %2', + deBits: 0, + hasCRC: 1, + hasHeader: 1, + inputFrequencyOffset: 0, + message73: '%1 %2 73', + messageRepeat: 1, + messageType: 0, + myCall: 'MYCALL', + myLoc: 'AA00AA', + myRpt: '59', + nbParityBits: 1, + preambleChirps: 8, + qsoTextMessage: '%1 %2 %3', + quietMillis: 1000, + replyMessage: '%1 %2 %3', + replyReportMessage: '%1 %2 R%3', + reportMessage: '%1 %2 %3', + reverseAPIAddress: '127.0.0.1', + reverseAPIChannelIndex: 0, + reverseAPIDeviceIndex: 0, + reverseAPIPort: 8888, + rgbColor: -65281, + rrrMessage: '%1 %2 RRR', + spreadFactor: 7, + syncWord: 52, + textMessage: 'Hello LoRa', + title: 'ChirpChat Modulator', + udpAddress: '127.0.0.1', + udpEnabled: 0, + udpPort: 9998, + urCall: 'URCALL', + useReverseAPI: 0 +}; + +export interface ChirpChatModReport { + channelPowerDB: number; + channelSampleRate: number; + payloadTimeMs: number; + symbolTimeMs: number; + totalTimeMs: number; +} + +export const CHIRPCHATMOD_REPORT_DEFAULT = { + channelPowerDB: -120, + channelSampleRate: 0, + payloadTimeMs: 0, + symbolTimeMs: 32.77009582519531, + totalTimeMs: 401.4336853027344 +}; diff --git a/src/app/channel-details/ieee802154-mod/ieee802154-mod.component.css b/src/app/channel-details/ieee802154-mod/ieee802154-mod.component.css new file mode 100644 index 0000000..e3b5bee --- /dev/null +++ b/src/app/channel-details/ieee802154-mod/ieee802154-mod.component.css @@ -0,0 +1,91 @@ +.channel-card-header { + font-family: Arial, Helvetica, sans-serif; + font-size: 14px; + border: solid; + border-color: gray; + border-width: 1px; + margin-bottom: 5px; + padding: 0.2em 0.2em 0.2em 0.2em; +} + +.channel-header-tx { + background-color: rgb(255,200,170); +} + +.channel-header-comp { + margin-top: 1px; +} + +.button-card { + padding-top: 0px; + padding-right: 0px; + padding-bottom: 0px; + padding-left: 0px; + width: 22px; + height: 22px; +} + +.button-on { + border: solid; + border-width: 1px; + border-color: gray; + background-color: rgb(0, 255, 0, 1.0); +} + +.button-off { + border: solid; + border-width: 1px; + border-color: gray; + background-color: rgb(0, 0, 0, 0); +} + +.rfbw-input { + width: 6ch; +} + +.index-input { + width: 6ch; +} + +.data-input { + width: 80ch; +} + +.source-input { + width: 10ch; + background-color: white; +} + +td { + background-color: rgb(230, 230, 210); +} + +.status-ok-card { + border: outset; + border-color: rgb(134, 255, 110); + border-width: 2px; + margin-bottom: 5px; + padding: 0.2em 0.2em 0.2em 0.2em; +} + +.status-ko-card { + border: solid; + border-color: rgb(255, 134, 110); + border-width: 2px; + padding: 0.2em 0.2em 0.2em 0.2em; +} + +::ng-deep .mat-card { + /* CSS styles go here */ + padding: 0px; /* for example to remove the margin */ +} + +::ng-deep .mat-checkbox-inner-container { + height: 14px!important; + width: 14px!important; + background-color: white; +} + +::ng-deep .mat-checkbox-label { + line-height: 16px!important; +} diff --git a/src/app/channel-details/ieee802154-mod/ieee802154-mod.component.html b/src/app/channel-details/ieee802154-mod/ieee802154-mod.component.html new file mode 100644 index 0000000..4f14fbf --- /dev/null +++ b/src/app/channel-details/ieee802154-mod/ieee802154-mod.component.html @@ -0,0 +1,366 @@ + + +   +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + Δf {{getDeltaFrequency()}} kHz +
+ + Standard + + + {{txStandard.viewValue}} + + +   + + + + Modulation + + + {{txModulation.viewValue}} + + +
+ Rate + + + {{bps.viewValue}} + + + + RFBW + + kHz + + Gain + + dB +
+ Data + +
+ + +   + Chip rate {{ getChipRate() }} S/s + + + + Mute + + + LPF taps + +
+ + Repeat + + + Delay + + s + + Number + + + {{txNbMessage.viewValue}} + + +
+ Filter + + + {{pulseShaping.viewValue}} + + + + Roll-off + + + Span + +
+ + Scrambling + + + + Standard + + + {{polynomialStandard.viewValue}} + + +   + + + + Polynomial + +
+ + + Modulate + + Ramp up + + + + Ramp down + + + Ramp range + + dB +
+ + Sub GHz + + + + BB Noise + + + + Write to csv + +
+ + Bytes from UDP + + + Addr + + + Port + +
+ + Reverse API + + + Device + + + Channel + +
+ Addr + + + Port + +
+ + + + +
+
+
+ + {{ statusMessage }} + diff --git a/src/app/channel-details/ieee802154-mod/ieee802154-mod.component.spec.ts b/src/app/channel-details/ieee802154-mod/ieee802154-mod.component.spec.ts new file mode 100644 index 0000000..9a51f22 --- /dev/null +++ b/src/app/channel-details/ieee802154-mod/ieee802154-mod.component.spec.ts @@ -0,0 +1,58 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatOptionModule } from '@angular/material/core'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatSelectModule } from '@angular/material/select'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { ActivatedRoute, RouterModule } from '@angular/router'; +import { ColorPickerModule } from 'ngx-color-picker'; +import { CommonComponentsModule } from 'src/app/common-components/common-components.module'; +import { ChannelHeaderComponent } from '../channel-header/channel-header.component'; +import { ChannelMonitorComponent } from '../channel-monitor/channel-monitor.component'; + +import { Ieee802154ModComponent } from './ieee802154-mod.component'; + +describe('Ieee802154ModComponent', () => { + let component: Ieee802154ModComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + Ieee802154ModComponent, + ChannelHeaderComponent, + ChannelMonitorComponent + ], + imports: [ + RouterModule, + FormsModule, + MatCardModule, + MatSelectModule, + MatOptionModule, + MatCheckboxModule, + MatTooltipModule, + MatProgressBarModule, + HttpClientTestingModule, + CommonComponentsModule, + ColorPickerModule + ], + providers: [ + {provide: ActivatedRoute, useValue: { snapshot: {parent: {params: {dix: 0, cix: 0}}}}} + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(Ieee802154ModComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/channel-details/ieee802154-mod/ieee802154-mod.component.ts b/src/app/channel-details/ieee802154-mod/ieee802154-mod.component.ts new file mode 100644 index 0000000..45b9b2d --- /dev/null +++ b/src/app/channel-details/ieee802154-mod/ieee802154-mod.component.ts @@ -0,0 +1,556 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { interval, Subscription } from 'rxjs'; +import { Utils } from 'src/app/common-components/utils'; +import { DeviceStoreService } from 'src/app/device-store.service'; +import { DevicesetService } from 'src/app/deviceset/deviceset/deviceset.service'; +import { SdrangelUrlService } from 'src/app/sdrangel-url.service'; +import { ChannelActions, ChannelSettings } from '../channel-details'; +import { ChannelDetailsService } from '../channel-details.service'; +import { IEEE_802_15_4_ModActions, IEEE_802_15_4_MODACTIONS_DEFAULT, IEEE_802_15_4_ModReport, IEEE_802_15_4_MODREPORT_DEFAULT, IEEE_802_15_4_ModSettings, IEEE_802_15_4_MODSETTNGS_DEFAULT } from './ieee802154-mod'; + +export interface Standard { + value: number; + viewValue: string; +} + +export interface Modulation { + value: number; + viewValue: string; +} + +export interface BPS { + value: number; + viewValue: string; +} + +export interface TxNbMessages { + value: number; + viewValue: string; +} + +export interface PolynomialStandard { + value: number; + viewValue: string; +} + +export interface PulseShaping { + value: number; + viewValue: string; +} + +@Component({ + selector: 'app-ieee802154-mod', + templateUrl: './ieee802154-mod.component.html', + styleUrls: ['./ieee802154-mod.component.css'] +}) +export class Ieee802154ModComponent implements OnInit { + deviceIndex: number; + channelIndex: number; + sdrangelURL: string; + settings: IEEE_802_15_4_ModSettings = IEEE_802_15_4_MODSETTNGS_DEFAULT; + report: IEEE_802_15_4_ModReport = IEEE_802_15_4_MODREPORT_DEFAULT; + actions: IEEE_802_15_4_ModActions = IEEE_802_15_4_MODACTIONS_DEFAULT; + deviceCenterFrequency: number; + deviceBasebandRate: number; + deviceStoreSubscription: Subscription; + channelReportSubscription: Subscription; + channelDeltaFrequency: number; + channelCenterFrequencyKhz: number; + channelMinFrequencyKhz: number; + channelMaxFrequencyKhz: number; + rfBandwidthKhz: number; + statusMessage: string; + statusError = false; + rgbTitle: number[] = [0, 0, 0]; + rgbTitleStr = 'rgb(0,0,0)'; + monitor: boolean; + bbNoise: boolean; + channelMute: boolean; + modulateWhileRamping: boolean; + repeat: boolean; + scramble: boolean; + subGHzBand: boolean; + udpEnabled: boolean; + useReverseAPI: boolean; + writeToFile: boolean; + txStandards: Standard[] = [ + {value: 0, viewValue: '20kbps BPSK'}, + {value: 1, viewValue: '40kbps BPSK'}, + {value: 2, viewValue: '100kbps <1GHz O-QPSK'}, + {value: 3, viewValue: '250kbps <1GHz O-QPSK (Sine)'}, + {value: 4, viewValue: '250kbps <1GHz O-QPSK (RC)'}, + {value: 5, viewValue: '250kbps >2GHz O-QPSK'}, + ]; + txStandard = 0; + txModulations: Modulation[] = [ + {value: 0, viewValue: 'BPSK'}, + {value: 1, viewValue: 'O-QPSK'} + ]; + txBPSs: BPS[] = [ + {value: 1200, viewValue: '1,2kbps'}, + {value: 2400, viewValue: '2.4kbps'}, + {value: 4800, viewValue: '4.8kbps'}, + {value: 9600, viewValue: '9.6kbps'}, + {value: 20000, viewValue: '20kbps'}, + {value: 40000, viewValue: '40kbps'}, + {value: 100000, viewValue: '100kbps'}, + {value: 250000, viewValue: '250kbps'}, + ]; + txNbMessages: TxNbMessages[] = [ + {value: -1, viewValue: 'Infinite'}, + {value: 10, viewValue: '10'}, + {value: 100, viewValue: '100'}, + {value: 1000, viewValue: '1000'}, + ]; + polynomialStandards: PolynomialStandard[] = [ + {value: 0x108, viewValue: '0x108'} + ]; + polynomialStandard = 0x108; + pulseShapings: PulseShaping[] = [ + {value: 0, viewValue: 'Raised Cosine'}, + {value: 1, viewValue: 'Half Sine'} + ]; + + constructor(private route: ActivatedRoute, + private channeldetailsService: ChannelDetailsService, + private deviceSetService: DevicesetService, + private sdrangelUrlService: SdrangelUrlService, + private deviceStoreService: DeviceStoreService) { + this.deviceStoreSubscription = null; + this.channelReportSubscription = null; + this.monitor = false; + this.sdrangelUrlService.currentUrlSource.subscribe(url => { + this.sdrangelURL = url; + }); + } + + ngOnInit(): void { + this.deviceIndex = +this.route.snapshot.parent.params['dix']; + this.channelIndex = +this.route.snapshot.parent.params['cix']; + this.getDeviceStorage(); + this.getChannelSettings(); + } + + getChannelSettings() { + this.channeldetailsService.getSettings(this.sdrangelURL, this.deviceIndex, this.channelIndex).subscribe( + channelSettings => { + if (channelSettings.channelType === 'IEEE_802_15_4_Mod') { + this.statusMessage = 'OK'; + this.statusError = false; + this.settings = channelSettings.IEEE_802_15_4_ModSettings; + this.channelDeltaFrequency = this.settings.inputFrequencyOffset; + this.channelCenterFrequencyKhz = (this.deviceCenterFrequency + this.channelDeltaFrequency) / 1000; + this.channelMaxFrequencyKhz = (this.deviceCenterFrequency + (this.deviceBasebandRate / 2)) / 1000; + this.channelMinFrequencyKhz = (this.deviceCenterFrequency - (this.deviceBasebandRate / 2)) / 1000; + this.rfBandwidthKhz = this.settings.rfBandwidth / 1000; + this.rgbTitle = Utils.intToRGB(this.settings.rgbColor); + this.rgbTitleStr = this.getRGBTitleStr(); + this.bbNoise = this.settings.bbNoise !== 0; + this.channelMute = this.settings.channelMute !== 0; + this.modulateWhileRamping = this.settings.modulateWhileRamping !== 0; + this.repeat = this.settings.repeat !== 0; + this.scramble = this.settings.scramble !== 0; + this.subGHzBand = this.settings.subGHzBand !== 0; + this.udpEnabled = this.settings.udpEnabled !== 0; + this.useReverseAPI = this.settings.useReverseAPI !== 0; + this.writeToFile = this.settings.writeToFile !== 0; + this.settings.data = this.settings.data ?? ''; + } else { + this.statusMessage = 'Not a PacketMod channel'; + this.statusError = true; + } + } + ); + } + + getRGBTitleStr(): string { + return 'rgb(' + this.rgbTitle[0].toString() + ',' + this.rgbTitle[1].toString() + ',' + this.rgbTitle[2].toString() + ')'; + } + + private getDeviceStorage() { + this.deviceStoreSubscription = this.deviceStoreService.get(this.deviceIndex).subscribe( + deviceStorage => { + this.deviceCenterFrequency = deviceStorage.centerFrequency; + this.deviceBasebandRate = deviceStorage.basebandRate; + }, + error => { + if (error === 'No device at this index') { + this.deviceSetService.getInfo(this.sdrangelURL, this.deviceIndex).subscribe( + deviceset => { + this.deviceStoreService.change( + this.deviceIndex, + { + basebandRate: deviceset.samplingDevice.bandwidth, + centerFrequency: deviceset.samplingDevice.centerFrequency + } + ); + this.deviceBasebandRate = deviceset.samplingDevice.bandwidth; + this.deviceCenterFrequency = deviceset.samplingDevice.centerFrequency; + } + ); + } + } + ); + } + + private setChannelSettings(ieee802154ModSettings: IEEE_802_15_4_ModSettings) { + const settings: ChannelSettings = {}; + settings.channelType = 'IEEE_802_15_4_Mod'; + settings.direction = 1, + settings.IEEE_802_15_4_ModSettings = ieee802154ModSettings; + this.channeldetailsService.setSettings(this.sdrangelURL, this.deviceIndex, this.channelIndex, settings).subscribe( + res => { + console.log('Set settings OK', res); + this.statusMessage = 'OK'; + this.statusError = false; + this.getChannelSettings(); + }, + error => { + this.statusMessage = error.message; + this.statusError = true; + } + ); + } + + private postChannelActions(ieee802154ModActions: IEEE_802_15_4_ModActions) { + const actions: ChannelActions = {}; + actions.channelType = 'IEEE_802_15_4_Mod'; + actions.direction = 1, + actions.IEEE_802_15_4_ModActions = ieee802154ModActions; + this.channeldetailsService.postAction(this.sdrangelURL, this.deviceIndex, this.channelIndex, actions).subscribe( + res => { + this.statusMessage = 'OK'; + this.statusError = false; + this.getChannelSettings(); + }, + error => { + this.statusMessage = 'Cannot post tx action'; + this.statusError = true; + } + ); + } + + enableReporting(enable: boolean) { + if (enable) { + this.channelReportSubscription = interval(1000).subscribe( + _ => { + this.channeldetailsService.getReport(this.sdrangelURL, this.deviceIndex, this.channelIndex).subscribe( + channelReport => { + if (channelReport.channelType === 'IEEE_802_15_4_Mod') { + this.report = channelReport.IEEE_802_15_4_ModReport; + } + } + ); + } + ); + } else { + this.channelReportSubscription.unsubscribe(); + this.channelReportSubscription = null; + } + } + + toggleMonitor() { + this.monitor = !this.monitor; + this.enableReporting(this.monitor); + } + + onFrequencyUpdate(frequency: number) { + this.channelCenterFrequencyKhz = frequency; + this.setCenterFrequency(); + } + + setCenterFrequency() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.inputFrequencyOffset = this.channelCenterFrequencyKhz * 1000 - this.deviceCenterFrequency; + this.setChannelSettings(newSettings); + } + + getDeltaFrequency(): number { + const frequency = this.channelCenterFrequencyKhz - (this.deviceCenterFrequency / 1000); + return +frequency.toFixed(3); + } + + onTitleColorChanged(colorStr: string) { + this.rgbTitleStr = colorStr; + this.setTitleColor(); + } + + setTitleColor() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.rgbColor = Utils.rgbToInt(this.rgbTitleStr); + this.setChannelSettings(newSettings); + } + + onTitleChanged(title: string) { + this.settings.title = title; + this.setTitle(); + } + + setTitle() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.title = this.settings.title; + this.setChannelSettings(newSettings); + } + + setRFBandwidth() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.rfBandwidth = this.rfBandwidthKhz * 1000; + this.setChannelSettings(newSettings); + } + + setWriteToFile() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.writeToFile = this.writeToFile ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setUdpEnabled() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.udpEnabled = this.udpEnabled ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setUDpAddress() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.udpAddress = this.settings.udpAddress; + this.setChannelSettings(newSettings); + } + + setUdpPort() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.udpPort = this.settings.udpPort; + this.setChannelSettings(newSettings); + } + + setUseReverseAPI() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.useReverseAPI = this.useReverseAPI ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setReverseAPIAddress() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.reverseAPIAddress = this.settings.reverseAPIAddress; + this.setChannelSettings(newSettings); + } + + setReverseAPIPort() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.reverseAPIPort = this.settings.reverseAPIPort; + this.setChannelSettings(newSettings); + } + + setReverseAPIDeviceIndex() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.reverseAPIDeviceIndex = this.settings.reverseAPIDeviceIndex; + this.setChannelSettings(newSettings); + } + + setReverseAPIChannelIndex() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.reverseAPIChannelIndex = this.settings.reverseAPIChannelIndex; + this.setChannelSettings(newSettings); + } + + setModulation() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.modulation = this.settings.modulation; + this.setChannelSettings(newSettings); + } + + setBbNoise() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.bbNoise = this.bbNoise ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setSubGHzBand() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.subGHzBand = this.subGHzBand ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setRampRange() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.rampRange = this.settings.rampRange; + this.setChannelSettings(newSettings); + } + + setRampDownBits() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.rampDownBits = this.settings.rampDownBits; + this.setChannelSettings(newSettings); + } + + setRampUpBits() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.rampUpBits = this.settings.rampUpBits; + this.setChannelSettings(newSettings); + } + + setModulateWhileRamping() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.modulateWhileRamping = this.modulateWhileRamping ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setPolynomial() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.polynomial = this.settings.polynomial; + this.setChannelSettings(newSettings); + } + + setPolynomialStandard() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + if (this.polynomialStandard === 0) { // 0x108 + newSettings.polynomial = 0x108; + this.setChannelSettings(newSettings); + } + } + + setScramble() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.scramble = this.scramble ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setSymbolSpan() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.symbolSpan = this.settings.symbolSpan; + this.setChannelSettings(newSettings); + } + + setBeta() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.beta = this.settings.beta; + this.setChannelSettings(newSettings); + } + + setPulseShaping() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.pulseShaping = this.settings.pulseShaping; + this.setChannelSettings(newSettings); + } + + setRepeatCount() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.repeatCount = this.settings.repeatCount; + this.setChannelSettings(newSettings); + } + + setRepeatDelay() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.repeatDelay = this.settings.repeatDelay; + this.setChannelSettings(newSettings); + } + + setRepeat() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.repeat = this.repeat ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setLpfTaps() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.lpfTaps = this.settings.lpfTaps; + this.setChannelSettings(newSettings); + } + + setChannelMute() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.channelMute = this.channelMute ? 1 : 0; + this.setChannelSettings(newSettings); + } + + setData() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.data = this.settings.data; + this.setChannelSettings(newSettings); + } + + setGain() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.gain = this.settings.gain; + this.setChannelSettings(newSettings); + } + + setBitRate() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + newSettings.bitRate = this.settings.bitRate; + newSettings.rfBandwidth = this.setBandwidthFromBitrate(newSettings.bitRate); + this.setChannelSettings(newSettings); + } + + private setBandwidthFromBitrate(bitRate: number): number { + return 2.0 * bitRate * 15.0; + } + + setTxStandard() { + const newSettings: IEEE_802_15_4_ModSettings = {}; + if (this.txStandard === 0) { // 20 kbps BPSK + newSettings.bitRate = 20000; + newSettings.subGHzBand = 1; + newSettings.pulseShaping = 0; // Raised Cosine + newSettings.modulation = 0; // BPSK + newSettings.beta = 1; + } else if (this.txStandard === 1) { // 40 kbps BPSK + newSettings.bitRate = 40000; + newSettings.subGHzBand = 1; + newSettings.pulseShaping = 0; // Raised Cosine + newSettings.modulation = 0; // BPSK + newSettings.beta = 1; + } else if (this.txStandard === 2) { // 100 kbps O-QPSK + newSettings.bitRate = 100000; + newSettings.subGHzBand = 1; + newSettings.pulseShaping = 0; // Raised Cosine + newSettings.modulation = 1; // O-QPSK + newSettings.beta = 0.8; + } else if (this.txStandard === 3) { // 250 kbps O-QPSK Half Sine + newSettings.bitRate = 250000; + newSettings.subGHzBand = 1; + newSettings.pulseShaping = 1; // Half Sine + newSettings.modulation = 1; // O-QPSK + newSettings.beta = 1; + } else if (this.txStandard === 4) { // 250 kbps O-QPSK Rasied Cosine + newSettings.bitRate = 250000; + newSettings.subGHzBand = 1; + newSettings.pulseShaping = 0; // Raised Cosine + newSettings.modulation = 1; // O-QPSK + newSettings.beta = 0.8; + } else if (this.txStandard === 5) { // 250 kbps O-QPSK Half Sine > 2GHz + newSettings.bitRate = 250000; + newSettings.subGHzBand = 0; + newSettings.pulseShaping = 0; // Raised Cosine + newSettings.modulation = 1; // O-QPSK + newSettings.beta = 0.8; + } + + if ((this.txStandard >= 0) && (this.txStandard <= 5)) { + newSettings.symbolSpan = 6; + newSettings.rfBandwidth = this.setBandwidthFromBitrate(newSettings.bitRate); + newSettings.spectrumRate = newSettings.rfBandwidth; + this.setChannelSettings(newSettings); + } + } + + tx() { + const newActions: IEEE_802_15_4_ModActions = {}; + newActions.tx = 1; + this.postChannelActions(newActions); + } + + getChipRate(): number { + let chipsPerSymbol, bitsPerSymbol; + + if (this.settings.modulation === 0) { // BPSK + chipsPerSymbol = 15; + bitsPerSymbol = 1; + } else { // O-QPSK + bitsPerSymbol = 4; + chipsPerSymbol = this.settings.subGHzBand ? 16 : 32; + } + + return this.settings.bitRate * chipsPerSymbol / bitsPerSymbol; + } +} diff --git a/src/app/channel-details/ieee802154-mod/ieee802154-mod.ts b/src/app/channel-details/ieee802154-mod/ieee802154-mod.ts new file mode 100644 index 0000000..e7d7937 --- /dev/null +++ b/src/app/channel-details/ieee802154-mod/ieee802154-mod.ts @@ -0,0 +1,100 @@ +// tslint:disable-next-line:class-name +export interface IEEE_802_15_4_ModSettings { + bbNoise: number; // boolean + beta: number; + bitRate: number; + channelMute: number; // boolean + data: string; + gain: number; + inputFrequencyOffset: number; + lpfTaps: number; + modulateWhileRamping: number; // boolean + modulation: number; // IEEE_802_15_4_ModSettings::Modulation + polynomial: number; + pulseShaping: number; // IEEE_802_15_4_ModSettings::PulseShaping + rampDownBits: number; + rampRange: number; + rampUpBits: number; + repeat: number; // boolean + repeatCount: number; // -1 for infinite + repeatDelay: number; + reverseAPIAddress: string; + reverseAPIChannelIndex: number; + reverseAPIDeviceIndex: number; + reverseAPIPort: number; + rfBandwidth: number; + rgbColor: number; + scramble: number; // boolean + spectrumRate: number; + streamIndex: number; + subGHzBand: number; // boolean + symbolSpan: number; + title: string; + udpAddress: string; + udpBytesFormat: number; // 0: hex string, 1: bytes + udpEnabled: number; // boolean + udpPort: number; + useReverseAPI: number; // boolean + writeToFile: number; // boolean +} + +export const IEEE_802_15_4_MODSETTNGS_DEFAULT = { + bbNoise: 0, + beta: 1, + bitRate: 20000, + channelMute: 0, + data: '01 cc 00 be ba 00 11 22 33 44 55 66 77 be ba 88 99 aa bb cc dd ee ff 53 44 52 20 41 6e 67 65 6c 20 64 6f 65 73 20 31 35 2e 34 ', + gain: -1, + inputFrequencyOffset: 0, + lpfTaps: 301, + modulateWhileRamping: 1, + modulation: 0, + polynomial: 264, + pulseShaping: 0, + rampDownBits: 0, + rampRange: 0, + rampUpBits: 0, + repeat: 0, + repeatCount: -1, + repeatDelay: 1, + reverseAPIAddress: '127.0.0.1', + reverseAPIChannelIndex: 0, + reverseAPIDeviceIndex: 0, + reverseAPIPort: 8888, + rfBandwidth: 600000, + rgbColor: -65536, + scramble: 0, + spectrumRate: 600000, + streamIndex: 0, + subGHzBand: 1, + symbolSpan: 6, + title: '802.15.4 Modulator', + udpAddress: '127.0.0.1', + udpBytesFormat: 0, + udpEnabled: 0, + udpPort: 9998, + useReverseAPI: 0, + writeToFile: 0 +}; + +// tslint:disable-next-line:class-name +export interface IEEE_802_15_4_ModReport { + channelPowerDB: number; + channelSampleRate: number; +} + +export const IEEE_802_15_4_MODREPORT_DEFAULT = { + channelPowerDB: -120, + channelSampleRate: 800000 +}; + +// tslint:disable-next-line:class-name +export interface IEEE_802_15_4_ModActions { + tx: number; + data: string; +} + +export const IEEE_802_15_4_MODACTIONS_DEFAULT = { + tx: 0, + data: '01 cc 00 be ba 00 11 22 33 44 55 66 77 be ba 88 99 aa bb cc dd ee ff 53 44 52 20 41 6e 67 65 6c 20 64 6f 65 73 20 31 35 2e 34 ' +};