aboutsummaryrefslogtreecommitdiffstats
path: root/src/config.rs
blob: 68b5f4409e615c62ce67528f37c3582a992bc556 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
use std::{collections::HashMap, fs};
use anyhow::Context;
use log::error;
use serde::{Deserialize, Serialize};
use serde_json::json;

type Protection = u8;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Service {
    pub unique_id: String,
    pub sid: u32,
    pub ecc: u8,
    pub label: String,
    pub shortlabel: String,
    pub input_port: u16,
    pub bitrate: u32,
    pub protection: Protection,
}

impl Service {
    pub fn sid_hex(&self) -> String {
        format!("{:04X}", self.sid)
    }

    pub fn ecc_hex(&self) -> String {
        format!("{:02X}", self.ecc)
    }

    pub fn dump_to_service_json(&self) -> serde_json::Value {
        json!({
            "id": self.sid,
            "ecc": self.ecc,
            "label": self.label,
            "shortlabel": self.shortlabel,
        })
    }

    pub fn dump_to_subchannel_json(&self, id: u32) -> serde_json::Value {
        json!({
            "type": "dabplus",
            "bitrate": self.bitrate,
            "id": id,
            "protection": self.protection,

            "inputproto": "edi",
            "inputuri": format!("tcp://127.0.0.1:{}", self.input_port),
            "buffer-management": "prebuffering",
            "buffer": 40,
            "prebuffering": 20
        })
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Config {
    pub instance_name: String,
    pub dabmux_config_location: String,
    pub tist: bool,
    pub tist_offset: i32,
    // TODO tai_clock_bulletins
    pub ensemble_id: u16,
    pub ensemble_ecc: u8,
    pub ensemble_label: String,
    pub ensemble_shortlabel: String,
    pub output_edi_port: u16,
    pub services: Vec<Service>,
}

impl Config {
    pub fn ensemble_id_hex(&self) -> String {
        format!("{:04X}", self.ensemble_id)
    }

    pub fn ensemble_ecc_hex(&self) -> String {
        format!("{:02X}", self.ensemble_ecc)
    }
}

impl Default for Config {
    fn default() -> Self {
        Config {
            instance_name: "CHANGEME".to_owned(),
            dabmux_config_location: "/etc/odr-dabmux.json".to_owned(),
            tist: true,
            tist_offset: 0,
            ensemble_id: 0x4FFF,
            ensemble_ecc: 0xE1,
            ensemble_label: "OpenDigitalRadio".to_owned(),
            ensemble_shortlabel: "ODR".to_owned(),
            output_edi_port: 8951,
            services: vec![
               Service {
                   unique_id: "nothing".to_owned(),
                   sid: 0x4DAA,
                   ecc: 0xE1,
                   label: "nothing".to_owned(),
                   shortlabel: "no".to_owned(),
                   input_port: 9001,
                   bitrate: 128,
                   protection: 2
               }
            ],
        }
    }
}

const CONFIGFILE : &str = "odr-dabmux-gui-config.toml";

impl Config {
    pub fn load() -> anyhow::Result<Self> {
        if std::path::Path::new(CONFIGFILE).exists() {
            let file_contents = fs::read_to_string(CONFIGFILE)?;
            toml::from_str(&file_contents)
                .or_else(|e| {
                    error!("Failed to read existing config file: {}", e);
                    Ok(Default::default())
                })
        }
        else {
            Ok(Default::default())
        }
    }

    pub fn store(&self) -> anyhow::Result<()> {
        fs::write(CONFIGFILE, toml::to_string_pretty(&self)?)
            .context("writing config file")
    }

    pub fn write_dabmux_json(&self) -> anyhow::Result<()> {
        let now = chrono::Utc::now().to_rfc3339();

        let mut services = HashMap::new();
        for s in &self.services {
            let uid = format!("srv-{}", s.unique_id);
            services.insert(uid, s.dump_to_service_json());
        }

        let mut subchannels = HashMap::new();
        let mut id = 0;
        for s in &self.services {
            id += 1;
            let uid = format!("sub-{}", s.unique_id);
            subchannels.insert(uid, s.dump_to_subchannel_json(id));
        }

        let mut components = HashMap::new();
        for s in &self.services {
            components.insert(
                format!("comp-{}", s.unique_id),
                json!({
                    "service": format!("srv-{}", s.unique_id),
                    "subchannel": format!("sub-{}", s.unique_id),
                    "user-applications": {
                        "userapp": "slideshow"
                    }
                }));
        }

        let new_conf = json!({
            "_comment": format!("Generated at {} by odr-dabmux-gui", now),
            "general": {
                "dabmode": 1,
                "nbframes": 0,
                "syslog": false,
                "tist": self.tist,
                "tist_offset": self.tist_offset,
                "managementport": 12720
            },
            "remotecontrol": {
                "telnetport": 12721,
                "zmqendpoint": "tcp://lo:12722"
            },
            "ensemble": {
                "id": self.ensemble_id,
                "ecc": self.ensemble_ecc,
                "local-time-offset": "auto",
                "reconfig-counter": "hash",
                "label": self.ensemble_label,
                "shortlabel": self.ensemble_shortlabel
            },
            "services": services,
            "subchannels": subchannels,
            "components": components,
            "outputs": {
                "throttle": "simul://",
                "edi": {
                    "destinations": {
                        "example_tcp": {
                            "protocol": "tcp",
                            "listenport": self.output_edi_port
                        }
                    }
                }
            }
        });

        fs::write(&self.dabmux_config_location, serde_json::to_string_pretty(&new_conf)?)
            .context("writing dabmux config file")
    }
}