scannit_core/
models.rs

1#[derive(Debug)]
2
3/// Indicates whether a PeriodPass or Ticket uses the old-style fares and zones, or the new.
4/// 2010 is the old-style, while 2014 is the new-style.
5pub enum ProductCode {
6    FaresFor2010(u16), // Code type = 0
7    FaresFor2014(u16), // Code type = 1
8}
9
10impl ProductCode {
11    pub const FARES_2010_TYPE: u8 = 0;
12    pub const FARES_2014_TYPE: u8 = 1;
13
14    pub(crate) fn new(code_type: u8, value: u16) -> ProductCode {
15        if code_type == ProductCode::FARES_2010_TYPE {
16            ProductCode::FaresFor2010(value)
17        } else {
18            ProductCode::FaresFor2014(value)
19        }
20    }
21}
22
23impl From<&ProductCode> for u16 {
24    fn from(val: &ProductCode) -> Self {
25        match val {
26            ProductCode::FaresFor2010(v) | ProductCode::FaresFor2014(v) => *v,
27        }
28    }
29}
30
31/// The number of a boarded element.
32#[derive(Debug)]
33pub enum BoardingLocation {
34    NoneOrReserved,
35    BusNumber(u16),
36    TrainNumber(u16),
37    PlatformNumber(u16),
38}
39
40impl BoardingLocation {
41    pub(crate) fn new(boarding_area_type: u8, boarding_area_value: u16) -> BoardingLocation {
42        match boarding_area_type {
43            0 => BoardingLocation::NoneOrReserved,
44            1 => BoardingLocation::BusNumber(boarding_area_value),
45            2 => BoardingLocation::TrainNumber(boarding_area_value),
46            3 => BoardingLocation::PlatformNumber(boarding_area_value),
47            e => panic!("Given value ({}) for BoardingLocation not supported.", e),
48        }
49    }
50}
51
52impl From<&BoardingLocation> for u16 {
53    fn from(val: &BoardingLocation) -> Self {
54        match val {
55            BoardingLocation::NoneOrReserved => 0,
56            BoardingLocation::BusNumber(num)
57            | BoardingLocation::TrainNumber(num)
58            | BoardingLocation::PlatformNumber(num) => *num,
59        }
60    }
61}
62
63#[repr(u32)]
64#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
65/// This enum is pure speculation--the underlying value is a single bit. What else _could_ it mean?
66pub enum BoardingDirection {
67    /// Indicates that at the time of boarding, the transit medium  was headed toward the end of its route.
68    TowardEnd = 0,
69    /// Indicates that at the time of boarding, the transit medium was headed toward the start of its route.
70    TowardStart = 1,
71}
72
73impl From<u8> for BoardingDirection {
74    fn from(value: u8) -> Self {
75        match value {
76            0 => BoardingDirection::TowardEnd,
77            1 => BoardingDirection::TowardStart,
78            e => panic!("Given value ({}) for BoardingDirection not supported.", e),
79        }
80    }
81}
82
83/// Represents an area in which, or a vehicle for which, a ticket is valid.
84#[derive(Debug)]
85pub enum ValidityArea {
86    OldZone(u8),
87    Zone(Vec<ValidityZone>),
88    Vehicle(VehicleType),
89}
90
91impl ValidityArea {
92    pub const OLD_ZONE_TYPE: u8 = 0;
93    pub const VEHICLE_TYPE: u8 = 1;
94    pub const NEW_ZONE_TYPE: u8 = 2; // The docs LIE, and don't include this value. But it's there!
95
96    pub(crate) fn new(area_type: u8, area_value: u8) -> ValidityArea {
97        let mut zones: Vec<ValidityZone> = Vec::new();
98        match area_type {
99            // TODO: Wrap this a bit more nicely. It represents an old zone (i.e. Zone 1, Zone 2, Region etc)
100            ValidityArea::OLD_ZONE_TYPE => ValidityArea::OldZone(area_value),
101            ValidityArea::VEHICLE_TYPE => ValidityArea::Vehicle(VehicleType::from(area_value)),
102            ValidityArea::NEW_ZONE_TYPE => {
103                let from_zone = (area_value & 0b0011_1000) >> 3; // leftmost 3 bits
104                let to_zone = area_value & 0b0000_0111; // 3 bits to the right of that
105                for val in from_zone..=to_zone {
106                    zones.push(ValidityZone::from(val));
107                }
108                ValidityArea::Zone(zones)
109            }
110            e => panic!("Unsupported area type: {}", e),
111        }
112    }
113}
114
115/// The HSL fare zone(s) in which a ticket is valid.
116#[derive(Clone, Debug)]
117pub enum ValidityZone {
118    ZoneA = 0,
119    ZoneB = 1,
120    ZoneC = 2,
121    ZoneD = 3,
122    ZoneE = 4,
123    ZoneF = 5,
124    ZoneG = 6,
125    ZoneH = 7,
126}
127
128impl From<u8> for ValidityZone {
129    fn from(value: u8) -> Self {
130        match value {
131            0 => ValidityZone::ZoneA,
132            1 => ValidityZone::ZoneB,
133            2 => ValidityZone::ZoneC,
134            3 => ValidityZone::ZoneD,
135            4 => ValidityZone::ZoneE,
136            5 => ValidityZone::ZoneF,
137            6 => ValidityZone::ZoneG,
138            7 => ValidityZone::ZoneH,
139            e => panic!("Given value ({}) for ValidityZone not supported.", e),
140        }
141    }
142}
143
144impl From<&ValidityZone> for u8 {
145    fn from(value: &ValidityZone) -> Self {
146        match value {
147            ValidityZone::ZoneA => 0,
148            ValidityZone::ZoneB => 1,
149            ValidityZone::ZoneC => 2,
150            ValidityZone::ZoneD => 3,
151            ValidityZone::ZoneE => 4,
152            ValidityZone::ZoneF => 5,
153            ValidityZone::ZoneG => 6,
154            ValidityZone::ZoneH => 7,
155        }
156    }
157}
158
159#[derive(Debug)]
160pub enum ValidityLength {
161    Minutes(u8),
162    Hours(u8),
163    TwentyFourHourPeriods(u8),
164    Days(u8),
165}
166
167impl ValidityLength {
168    pub(crate) fn new(length_type: u8, length_value: u8) -> ValidityLength {
169        match length_type {
170            0 => ValidityLength::Minutes(length_value),
171            1 => ValidityLength::Hours(length_value),
172            2 => ValidityLength::TwentyFourHourPeriods(length_value),
173            3 => ValidityLength::Days(length_value),
174            e => panic!("Given value ({}) for ValidityLength type not supported.", e),
175        }
176    }
177}
178
179impl From<&ValidityLength> for u8 {
180    fn from(value: &ValidityLength) -> Self {
181        match value {
182            ValidityLength::Minutes(num) => *num,
183            ValidityLength::Hours(num) => *num,
184            ValidityLength::TwentyFourHourPeriods(num) => *num,
185            ValidityLength::Days(num) => *num,
186        }
187    }
188}
189
190/// The vehicle type on which this ticket is valid.
191#[derive(Debug)]
192pub enum VehicleType {
193    Undefined = 0,
194    Bus = 1,
195    Tram = 5,
196    Metro = 6,
197    Train = 7,
198    Ferry = 8,
199    ULine = 9,
200}
201
202impl From<u8> for VehicleType {
203    fn from(value: u8) -> Self {
204        match value {
205            0 => VehicleType::Undefined,
206            1 => VehicleType::Bus,
207            5 => VehicleType::Tram,
208            6 => VehicleType::Metro,
209            7 => VehicleType::Train,
210            8 => VehicleType::Ferry,
211            9 => VehicleType::ULine,
212            e => panic!("Given value ('{:?}') for VehicleType not supported.", e),
213        }
214    }
215}
216
217impl From<&VehicleType> for u8 {
218    fn from(value: &VehicleType) -> Self {
219        match value {
220            VehicleType::Undefined => 0,
221            VehicleType::Bus => 1,
222            VehicleType::Tram => 5,
223            VehicleType::Metro => 6,
224            VehicleType::Train => 7,
225            VehicleType::Ferry => 8,
226            VehicleType::ULine => 9,
227        }
228    }
229}
230
231#[repr(u32)]
232#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
233pub enum Language {
234    Finnish = 0,
235    Swedish = 1,
236    English = 2,
237}
238
239impl From<u8> for Language {
240    fn from(value: u8) -> Self {
241        match value {
242            0 => Language::Finnish,
243            1 => Language::Swedish,
244            2 => Language::English,
245            e => panic!("Given value ({}) for Language not supported.", e),
246        }
247    }
248}
249
250/// The type of device that sold the ticket, or recharged the card.
251#[derive(Debug)]
252pub enum SaleDevice {
253    ServicePointSalesDevice(u16),
254    DriverTicketMachine(u16),
255    CardReader(u16),
256    TicketMachine(u16),
257    Server(u16),
258    HSLSmallEquipment(u16),
259    ExternalServiceEquipment(u16),
260    Reserved(u16),
261}
262
263impl SaleDevice {
264    pub(crate) fn new(device_type: u8, device_number: u16) -> SaleDevice {
265        match device_type {
266            0 => SaleDevice::ServicePointSalesDevice(device_number),
267            1 => SaleDevice::DriverTicketMachine(device_number),
268            2 => SaleDevice::CardReader(device_number),
269            3 => SaleDevice::TicketMachine(device_number),
270            4 => SaleDevice::Server(device_number),
271            5 => SaleDevice::HSLSmallEquipment(device_number),
272            6 => SaleDevice::ExternalServiceEquipment(device_number),
273            7 => SaleDevice::Reserved(device_number),
274            e => panic!("Given value ({}) for SaleDeviceType not supported.", e),
275        }
276    }
277}
278
279impl From<&SaleDevice> for u16 {
280    fn from(val: &SaleDevice) -> Self {
281        match val {
282            SaleDevice::ServicePointSalesDevice(num) => *num,
283            SaleDevice::DriverTicketMachine(num) => *num,
284            SaleDevice::CardReader(num) => *num,
285            SaleDevice::TicketMachine(num) => *num,
286            SaleDevice::Server(num) => *num,
287            SaleDevice::HSLSmallEquipment(num) => *num,
288            SaleDevice::ExternalServiceEquipment(num) => *num,
289            SaleDevice::Reserved(num) => *num,
290        }
291    }
292}
293
294/// The type and value of area where boarding last happened.
295#[derive(Debug)]
296pub enum BoardingArea {
297    Zone(ValidityZone),
298    Vehicle(VehicleType),
299    ZoneCircle(u8), // Not sure what this is. One of the old-style regions?
300}
301
302impl BoardingArea {
303    pub(crate) fn new(area_type: u8, area_value: u8) -> BoardingArea {
304        match area_type {
305            0 => BoardingArea::Zone(ValidityZone::from(area_value)),
306            1 => BoardingArea::Vehicle(VehicleType::from(area_type)),
307            2 => BoardingArea::ZoneCircle(area_value),
308            e => panic!("Given value ({}) for BoardingArea type not supported.", e),
309        }
310    }
311}
312
313impl From<&BoardingArea> for u8 {
314    fn from(val: &BoardingArea) -> Self {
315        match val {
316            BoardingArea::Zone(zone) => u8::from(zone),
317            BoardingArea::Vehicle(vehicle_type) => u8::from(vehicle_type),
318            BoardingArea::ZoneCircle(zone_value) => *zone_value,
319        }
320    }
321}