Skip to content

Commit 12dbd66

Browse files
committed
initial commit
0 parents  commit 12dbd66

17 files changed

+1396
-0
lines changed

.gitignore

Whitespace-only changes.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Florian Thienel
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.

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# ftl/cabrillo
2+
3+
This little Go library to handle the [Cabrillo](https://wwrof.org/cabrillo/) file format for amateur radio contest log files.
4+
5+
## Use as a Go Library
6+
7+
To include `cabrillo` into your own projects as a library:
8+
9+
```shell
10+
go get github.com/ftl/cabrillo
11+
```
12+
13+
## License
14+
This software is published under the [MIT License](https://www.tldrlegal.com/l/mit).
15+
16+
Copyright [Florian Thienel](http://thecodingflow.com/)

cabrillo.go

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
// Package cabrillo implements the Cabrillo V3 file format as defined by the [WWROF]
2+
//
3+
// [WWROF] https://wwrof.org/cabrillo/
4+
package cabrillo
5+
6+
import (
7+
"strconv"
8+
"strings"
9+
"time"
10+
11+
"github.com/ftl/hamradio/callsign"
12+
"github.com/ftl/hamradio/locator"
13+
)
14+
15+
func NewLog() *Log {
16+
return &Log{
17+
Custom: make(map[Tag]string),
18+
QSOData: make([]QSO, 0, 2000),
19+
IgnoredQSOs: make([]QSO, 0, 2000),
20+
}
21+
}
22+
23+
type Log struct {
24+
CabrilloVersion string
25+
Callsign callsign.Callsign
26+
Contest ContestIdentifier
27+
Category Category
28+
Certificate bool
29+
ClaimedScore int
30+
Club string
31+
CreatedBy string
32+
Email string
33+
GridLocator locator.Locator
34+
Location string
35+
Name string
36+
Address Address
37+
Operators []callsign.Callsign
38+
Host callsign.Callsign
39+
Offtime Offtime
40+
Soapbox string
41+
Debug int
42+
Custom map[Tag]string
43+
QSOData []QSO
44+
IgnoredQSOs []QSO
45+
}
46+
47+
type Tag string
48+
49+
func (t Tag) IsCustom() bool {
50+
if t == XQSOTag {
51+
return false
52+
}
53+
return strings.HasPrefix(string(t), XPrefix)
54+
}
55+
56+
const (
57+
StartOfLogTag Tag = "START-OF-LOG"
58+
EndOfLogTag Tag = "END-OF-LOG"
59+
CallsignTag Tag = "CALLSIGN"
60+
ContestTag Tag = "CONTEST"
61+
CategoryAssistedTag Tag = "CATEGORY-ASSISTED"
62+
CategoryBandTag Tag = "CATEGORY-BAND"
63+
CategoryModeTag Tag = "CATEGORY-MODE"
64+
CategoryOperatorTag Tag = "CATEGORY-OPERATOR"
65+
CategoryPowerTag Tag = "CATEGORY-POWER"
66+
CategoryStationTag Tag = "CATEGORY-STATION"
67+
CategoryTimeTag Tag = "CATEGORY-TIME"
68+
CategoryTransmitterTag Tag = "CATEGORY-TRANSMITTER"
69+
CategoryOverlayTag Tag = "CATEGORY-OVERLAY"
70+
CertificateTag Tag = "CERTIFICATE"
71+
ClaimedScoreTag Tag = "CLAIMED-SCORE"
72+
ClubTag Tag = "CLUB"
73+
CratedByTag Tag = "CREATED-BY"
74+
EmailTag Tag = "EMAIL"
75+
GridLocatorTag Tag = "GRID-LOCATOR"
76+
LocationTag Tag = "LOCATION"
77+
NameTag Tag = "NAME"
78+
AddressTag Tag = "ADDRESS"
79+
AddressCityTag Tag = "ADDRESS-CITY"
80+
AddressStateProviceTag Tag = "ADDRESS-STATE-PROVINCE"
81+
AddressPostalcodeTag Tag = "ADDRESS-POSTALCODE"
82+
AddressCountryTag Tag = "ADDRESS-COUNTRY"
83+
OperatorsTag Tag = "OPERATORS"
84+
OfftimeTag Tag = "OFFTIME"
85+
SoapboxTag Tag = "SOAPBOX"
86+
QSOTag Tag = "QSO"
87+
XQSOTag Tag = "X-QSO"
88+
XPrefix = "X-"
89+
)
90+
91+
type ContestIdentifier string
92+
93+
type Category struct {
94+
Assisted CategoryAssisted
95+
Band CategoryBand
96+
Mode CategoryMode
97+
Operator CategoryOperator
98+
Power CategoryPower
99+
Station CategoryStation
100+
Time CategoryTime
101+
Transmitter CategoryTransmitter
102+
Overlay CategoryOverlay
103+
}
104+
105+
type CategoryAssisted string
106+
107+
func (c CategoryAssisted) Bool() bool {
108+
return c == Assisted
109+
}
110+
111+
const (
112+
Assisted CategoryAssisted = "ASSISTED"
113+
NonAssisted CategoryAssisted = "NON-ASSISTED"
114+
)
115+
116+
type CategoryBand string
117+
118+
const (
119+
BandAll CategoryBand = "ALL"
120+
Band160m CategoryBand = "160M"
121+
Band80m CategoryBand = "80M"
122+
Band40m CategoryBand = "40M"
123+
Band20m CategoryBand = "20M"
124+
Band15m CategoryBand = "15M"
125+
Band10m CategoryBand = "10M"
126+
Band6m CategoryBand = "6M"
127+
Band4m CategoryBand = "4M"
128+
Band2m CategoryBand = "2M"
129+
Band222 CategoryBand = "222"
130+
Band432 CategoryBand = "432"
131+
Band902 CategoryBand = "902"
132+
Band1_2G CategoryBand = "1.2G"
133+
Band2_3G CategoryBand = "2.3G"
134+
Band3_4G CategoryBand = "3.4G"
135+
Band5_6G CategoryBand = "5.7G"
136+
Band10G CategoryBand = "10G"
137+
Band24G CategoryBand = "24G"
138+
Band47G CategoryBand = "47G"
139+
Band75G CategoryBand = "75G"
140+
Band122G CategoryBand = "122G"
141+
Band134G CategoryBand = "134G"
142+
Band241G CategoryBand = "241G"
143+
BandLight CategoryBand = "LIGHT"
144+
BandVHF_3Band CategoryBand = "VHF-3-BAND"
145+
BandVHF_FMOnly CategoryBand = "VHF-FM-ONLY"
146+
)
147+
148+
type CategoryMode string
149+
150+
const (
151+
ModeCW CategoryMode = "CW"
152+
ModeDIGI CategoryMode = "DIGI"
153+
ModeFM CategoryMode = "FM"
154+
ModeRTTY CategoryMode = "RTTY"
155+
ModeSSB CategoryMode = "SSB"
156+
ModeMIXED CategoryMode = "MIXED"
157+
)
158+
159+
type CategoryOperator string
160+
161+
const (
162+
SingleOperator CategoryOperator = "SINGLE-OP"
163+
MultiOperator CategoryOperator = "MULTI-OP"
164+
Checklog CategoryOperator = "CHECKLOG"
165+
)
166+
167+
type CategoryPower string
168+
169+
const (
170+
HighPower CategoryPower = "HIGH"
171+
LowPower CategoryPower = "LOW"
172+
QRP CategoryPower = "QRP"
173+
)
174+
175+
type CategoryStation string
176+
177+
const (
178+
DistributedStation CategoryStation = "DISTRIBUTED"
179+
FixedStation CategoryStation = "FIXED"
180+
MobileStation CategoryStation = "MOBILE"
181+
PortableStation CategoryStation = "PORTABLE"
182+
RoverStation CategoryStation = "ROVER"
183+
RoverLimitedStation CategoryStation = "ROVER-LIMITED"
184+
RoverUnlimitedStation CategoryStation = "ROVER-UNLIMITED"
185+
ExpeditionStation CategoryStation = "EXPEDITION"
186+
HQStation CategoryStation = "HQ"
187+
SchoolStation CategoryStation = "SCHOOL"
188+
ExplorerStation CategoryStation = "EXPLORER"
189+
)
190+
191+
type CategoryTime string
192+
193+
var durations = map[CategoryTime]time.Duration{
194+
Hours6: 6 * time.Hour,
195+
Hours8: 8 * time.Hour,
196+
Hours12: 12 * time.Hour,
197+
Hours24: 24 * time.Hour,
198+
}
199+
200+
func (c CategoryTime) Duration() time.Duration {
201+
return durations[c]
202+
}
203+
204+
const (
205+
Hours6 CategoryTime = "6-HOURS"
206+
Hours8 CategoryTime = "8-HOURS"
207+
Hours12 CategoryTime = "12-HOURS"
208+
Hours24 CategoryTime = "24-HOURS"
209+
)
210+
211+
type CategoryTransmitter string
212+
213+
const (
214+
OneTransmitter CategoryTransmitter = "ONE"
215+
TwoTransmitter CategoryTransmitter = "TWO"
216+
LimitedTransmitter CategoryTransmitter = "LIMITED"
217+
UnlimitedTransmitter CategoryTransmitter = "UNLIMITED"
218+
SWL CategoryTransmitter = "SWL"
219+
)
220+
221+
type CategoryOverlay string
222+
223+
const (
224+
ClassicOverlay CategoryOverlay = "CLASSIC"
225+
RookieOverlay CategoryOverlay = "ROOKIE"
226+
TBWiresOverlay CategoryOverlay = "TB-WIRES"
227+
YouthOverlay CategoryOverlay = "YOUTH"
228+
NoviceTechOverlay CategoryOverlay = "NOVICE-TECH"
229+
Over50Overlay CategoryOverlay = "OVER-50"
230+
)
231+
232+
type Address struct {
233+
Text string
234+
City string
235+
StateProvince string
236+
Postalcode string
237+
Country string
238+
}
239+
240+
type Offtime struct {
241+
Begin time.Time
242+
End time.Time
243+
}
244+
245+
func (o Offtime) Duration() time.Duration {
246+
return o.End.Sub(o.Begin)
247+
}
248+
249+
type QSO struct {
250+
Frequency QSOFrequency
251+
Mode QSOMode
252+
Timestamp time.Time
253+
Sent QSOInfo
254+
Received QSOInfo
255+
Transmitter int
256+
}
257+
258+
type QSOFrequency string
259+
260+
func (f QSOFrequency) IsFrequency() bool {
261+
return f.ToKilohertz() != 0
262+
}
263+
264+
func (f QSOFrequency) ToKilohertz() int {
265+
kHz, err := strconv.Atoi(string(f))
266+
if err != nil {
267+
return 0
268+
}
269+
if kHz < 1800 {
270+
return 0
271+
}
272+
return kHz
273+
}
274+
275+
func (f QSOFrequency) ToBand() CategoryBand {
276+
if f.IsFrequency() {
277+
kHz := f.ToKilohertz()
278+
switch {
279+
case kHz < 3500:
280+
return Band160m
281+
case kHz < 7000:
282+
return Band80m
283+
case kHz < 14000:
284+
return Band40m
285+
case kHz < 21000:
286+
return Band20m
287+
case kHz < 28000:
288+
return Band15m
289+
default:
290+
return Band10m
291+
}
292+
}
293+
switch f {
294+
case "50":
295+
return Band6m
296+
case "70":
297+
return Band4m
298+
case "144":
299+
return Band2m
300+
default:
301+
return CategoryBand(strings.ToUpper(string(f)))
302+
}
303+
}
304+
305+
type QSOMode string
306+
307+
const (
308+
QSOModeCW QSOMode = "CW"
309+
QSOModePhone QSOMode = "PH"
310+
QSOModeFM QSOMode = "FM"
311+
QSOModeRTTY QSOMode = "RY"
312+
QSOModeDigi QSOMode = "DG"
313+
)
314+
315+
type QSOInfo struct {
316+
Call callsign.Callsign
317+
RST string
318+
Exchange []string
319+
}

go.mod

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module github.com/ftl/cabrillo
2+
3+
go 1.19
4+
5+
require (
6+
github.com/ftl/hamradio v0.2.5
7+
github.com/stretchr/testify v1.8.1
8+
)
9+
10+
require (
11+
github.com/davecgh/go-spew v1.1.1 // indirect
12+
github.com/pmezard/go-difflib v1.0.0 // indirect
13+
gopkg.in/yaml.v3 v3.0.1 // indirect
14+
)

go.sum

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/ftl/hamradio v0.2.5 h1:mjlTkMKDfcSmtHSwq+M4Co6lv76nvxIgUEDriINYAA0=
5+
github.com/ftl/hamradio v0.2.5/go.mod h1:FOZkf8liaM/H8F8Vyp36EN9iVzikKpaOJ5AqrW77cEo=
6+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
10+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
11+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
12+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
13+
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
14+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
15+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
16+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
17+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
18+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
19+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)