scannit_core/
eticket.rs

1use crate::conversion::*;
2use crate::en1545date::*;
3use crate::models::*;
4use chrono::prelude::*;
5use num_traits::Zero;
6
7#[derive(Debug)]
8pub struct ETicket {
9    /// If ProductCodeGroup is > 0, this returns ProductCodeGroup
10    pub product_code: ProductCode,
11    /// If CustomerProfileGroup is > 0, this returns CustomerProfileGroup.
12    pub customer_profile: u8,
13    pub language: Language,
14    /// If ValidityLengthGroup is > 0 this returns ValidityLengthGroup.
15    pub validity_length: ValidityLength,
16    pub validity_area: ValidityArea,
17    pub sale_datetime: DateTime<Utc>,
18    pub sale_device: SaleDevice,
19    /// If TicketFareGroup is > 0 this returns TicketFareGroup.
20    pub ticket_fare_cents: u16,
21    pub group_size: u8,
22
23    // --- Extension ticket stuff ---
24    /// if true, this is an extra zone on top of a PeriodPass.
25    pub extra_zone: bool,
26    /// The validity area for the PeriodPass associated with the extra zone ticket.
27    pub period_pass_validity_area: ValidityArea,
28    pub extension_product_code: ProductCode,
29    pub extension_1_validity_area: ValidityArea,
30    pub extension_1_fare_cents: u16,
31    pub extension_2_validity_area: ValidityArea,
32    pub extension_2_fare_cents: u16,
33    pub sale_status: bool,
34
35    // --- Ticket validity info ---
36    pub validity_start_datetime: DateTime<Utc>,
37    /// If ValidityEndDateGroup and ValidityEndTimeGroup are > 0 this uses them instead.
38    pub validity_end_datetime: DateTime<Utc>,
39    /// True if the ticket is currently valid.
40    pub validity_status: bool,
41
42    // --- Boarding info ---
43    pub boarding_datetime: DateTime<Utc>,
44    pub boarding_vehicle: u16,
45    pub boarding_location: BoardingLocation,
46    pub boarding_direction: BoardingDirection,
47    pub boarding_area: BoardingArea,
48}
49
50pub fn create_e_ticket(e_ticket: &[u8]) -> ETicket {
51    let product_code_type = get_bits_as_u8(e_ticket, 0, 1);
52    let product_code_value = get_bits_as_u16(e_ticket, 1, 14);
53    let product_code_group_value = get_bits_as_u16(e_ticket, 15, 14);
54    let product_code = single_or_group(product_code_value, product_code_group_value);
55
56    let customer_profile_value = get_bits_as_u8(e_ticket, 29, 5);
57    let customer_profile_group_value = get_bits_as_u8(e_ticket, 34, 5);
58    let customer_profile = single_or_group(customer_profile_value, customer_profile_group_value);
59    let language_code = get_bits_as_u8(e_ticket, 39, 2);
60
61    let validity_length_type_value = get_bits_as_u8(e_ticket, 41, 2);
62    let validity_length_value = get_bits_as_u8(e_ticket, 43, 8);
63    let validity_length_type_group_value = get_bits_as_u8(e_ticket, 51, 2);
64    let validity_length_group_value = get_bits_as_u8(e_ticket, 53, 8);
65    let validity_length_type =
66        single_or_group(validity_length_type_value, validity_length_type_group_value);
67    let validity_length = single_or_group(validity_length_value, validity_length_group_value);
68    let validity_area_type = get_bits_as_u8(e_ticket, 61, 2);
69    let validity_area_value = get_bits_as_u8(e_ticket, 63, 6);
70
71    let sale_date = get_bits_as_u16(e_ticket, 69, 14);
72    let sale_hour_as_minutes = get_bits_as_u16(e_ticket, 83, 5) * 60; // Turned into minutes so we can just stuff it into the conversion function.
73    let sale_datetime = from_en1545_date_and_time(sale_date, sale_hour_as_minutes);
74    let sale_device_type = get_bits_as_u8(e_ticket, 88, 3);
75    let sale_device_number = get_bits_as_u16(e_ticket, 91, 14);
76
77    let ticket_fare_value = get_bits_as_u16(e_ticket, 105, 14);
78    let ticket_fare_group_value = get_bits_as_u16(e_ticket, 119, 14);
79    let ticket_fare = single_or_group(ticket_fare_value, ticket_fare_group_value);
80    let group_size = get_bits_as_u8(e_ticket, 133, 6);
81    let extra_zone = get_bits_as_u8(e_ticket, 139, 1) != 0;
82
83    let period_pass_validity_area = get_bits_as_u8(e_ticket, 140, 6);
84    let extension_product_code = get_bits_as_u16(e_ticket, 146, 14);
85    let extension_1_validity_area = get_bits_as_u8(e_ticket, 160, 6);
86    let extension_1_fare_cents = get_bits_as_u16(e_ticket, 166, 14);
87    let extension_2_validity_area = get_bits_as_u8(e_ticket, 180, 6);
88    let extension_2_fare_cents = get_bits_as_u16(e_ticket, 186, 14);
89    let sale_status = get_bits_as_u8(e_ticket, 200, 1) != 0;
90
91    let validity_start_date = get_bits_as_u16(e_ticket, 205, 14);
92    let validity_start_time = get_bits_as_u16(e_ticket, 219, 11);
93    let validity_end_date_value = get_bits_as_u16(e_ticket, 230, 14);
94    let validity_end_time_value = get_bits_as_u16(e_ticket, 244, 11);
95    let validity_end_date_group_value = get_bits_as_u16(e_ticket, 255, 14);
96    let valdiity_end_time_group_value = get_bits_as_u16(e_ticket, 269, 11);
97    let validity_end_datetime = from_en1545_date_and_time(
98        single_or_group(validity_end_date_value, validity_end_date_group_value),
99        single_or_group(validity_end_time_value, valdiity_end_time_group_value),
100    );
101    let validity_status = get_bits_as_u8(e_ticket, 285, 1) != 0;
102
103    let boarding_date = get_bits_as_u16(e_ticket, 286, 14);
104    let boarding_time = get_bits_as_u16(e_ticket, 300, 11);
105    let boarding_vehicle = get_bits_as_u16(e_ticket, 311, 14);
106    let boarding_location_num_type = get_bits_as_u8(e_ticket, 325, 2);
107    let boarding_location_num = get_bits_as_u16(e_ticket, 327, 14);
108    let boarding_direction = get_bits_as_u8(e_ticket, 341, 1);
109    let boarding_area_type = get_bits_as_u8(e_ticket, 342, 2);
110    let boarding_area = get_bits_as_u8(e_ticket, 344, 6);
111
112    ETicket {
113        product_code: ProductCode::new(product_code_type, product_code),
114        customer_profile,
115        language: Language::from(language_code),
116        validity_length: ValidityLength::new(validity_length_type, validity_length),
117        validity_area: ValidityArea::new(validity_area_type, validity_area_value),
118        sale_datetime,
119        sale_device: SaleDevice::new(sale_device_type, sale_device_number),
120        ticket_fare_cents: ticket_fare,
121        group_size,
122
123        extra_zone,
124        period_pass_validity_area: ValidityArea::new(
125            ValidityArea::OLD_ZONE_TYPE,
126            period_pass_validity_area,
127        ),
128        extension_product_code: ProductCode::new(
129            ProductCode::FARES_2014_TYPE,
130            extension_product_code,
131        ),
132        extension_1_validity_area: ValidityArea::new(
133            ValidityArea::OLD_ZONE_TYPE,
134            extension_1_validity_area,
135        ),
136        extension_1_fare_cents,
137        extension_2_validity_area: ValidityArea::new(
138            ValidityArea::OLD_ZONE_TYPE,
139            extension_2_validity_area,
140        ),
141        extension_2_fare_cents,
142        sale_status,
143
144        validity_start_datetime: from_en1545_date_and_time(
145            validity_start_date,
146            validity_start_time,
147        ),
148        validity_end_datetime,
149        validity_status,
150        boarding_datetime: from_en1545_date_and_time(boarding_date, boarding_time),
151        boarding_vehicle,
152        boarding_location: BoardingLocation::new(boarding_location_num_type, boarding_location_num),
153        boarding_direction: BoardingDirection::from(boarding_direction),
154        boarding_area: BoardingArea::new(boarding_area_type, boarding_area),
155    }
156}
157
158fn single_or_group<T: Zero + PartialOrd>(single: T, group: T) -> T {
159    if group > T::zero() {
160        group
161    } else {
162        single
163    }
164}