diff --git a/python/yaroc/clients/client.py b/python/yaroc/clients/client.py index 55bba29..5299592 100644 --- a/python/yaroc/clients/client.py +++ b/python/yaroc/clients/client.py @@ -7,7 +7,7 @@ from serial_asyncio import open_serial_connection from ..pb.status_pb2 import Status -from ..rs import SiPunch +from ..rs import SiPunchLog class Client(ABC): @@ -22,7 +22,7 @@ async def loop(self): pass @abstractmethod - async def send_punch(self, punch: SiPunch) -> bool: + async def send_punch(self, punch_log: SiPunchLog) -> bool: pass @abstractmethod @@ -69,12 +69,12 @@ async def loop(self): ) self.writer.write(msg) - async def send_punch(self, punch: SiPunch) -> bool: + async def send_punch(self, punch_log: SiPunchLog) -> bool: if self.writer is None: logging.error("Serial client not connected") return False try: - self.writer.write(bytes(punch.raw)) + self.writer.write(bytes(punch_log.raw)) logging.info("Punch sent via serial port") return True except serial.serialutil.SerialException as err: @@ -109,7 +109,7 @@ async def send_status(self, status: Status, mac_address: str) -> Sequence[bool | ClientGroup.handle_results(results) return results - async def send_punch(self, punch: SiPunch) -> Sequence[bool | BaseException]: + async def send_punch(self, punch: SiPunchLog) -> Sequence[bool | BaseException]: handles = [client.send_punch(punch) for client in self.clients] results = await asyncio.gather(*handles) ClientGroup.handle_results(results) diff --git a/python/yaroc/clients/mqtt.py b/python/yaroc/clients/mqtt.py index c9de3e6..296ca7b 100644 --- a/python/yaroc/clients/mqtt.py +++ b/python/yaroc/clients/mqtt.py @@ -13,7 +13,7 @@ from ..pb.punches_pb2 import Punch, Punches from ..pb.status_pb2 import Disconnected, Status from ..pb.utils import create_punch_proto -from ..rs import SiPunch +from ..rs import SiPunchLog from ..utils.async_serial import AsyncATCom from ..utils.modem_manager import ModemManager, NetworkType from ..utils.retries import BackoffBatchedRetries @@ -83,15 +83,15 @@ def get_topics(self, mac_addr: str) -> Topics: async def send_punch( self, - punch: SiPunch, + punch_log: SiPunchLog, ) -> bool: punches = Punches() try: - punches.punches.append(create_punch_proto(punch)) + punches.punches.append(create_punch_proto(punch_log)) except Exception as err: logging.error(f"Creation of Punch proto failed: {err}") punches.sending_timestamp.GetCurrentTime() - topics = self.get_topics(punch.host_info.mac_address) + topics = self.get_topics(punch_log.host_info.mac_address) return await self._send(topics.punch, punches.SerializeToString(), 1, "Punch") async def send_status(self, status: Status, mac_addr: str) -> bool: @@ -192,9 +192,9 @@ async def _send_punches(self, punches: list[Punch]) -> list[bool]: async def send_punch( self, - punch: SiPunch, + punch_log: SiPunchLog, ) -> bool: - res = await self._retries.send(create_punch_proto(punch)) + res = await self._retries.send(create_punch_proto(punch_log)) return res if res is not None else False async def send_status(self, status: Status, mac_addr: str) -> bool: diff --git a/python/yaroc/clients/roc.py b/python/yaroc/clients/roc.py index 2e35714..3ef8fe3 100644 --- a/python/yaroc/clients/roc.py +++ b/python/yaroc/clients/roc.py @@ -7,7 +7,7 @@ from aiohttp_retry import ExponentialRetry, RetryClient from ..pb.status_pb2 import EventType, Status -from ..rs import SiPunch +from ..rs import SiPunchLog from ..utils.modem_manager import NetworkType from ..utils.sys_info import FREQ_MULTIPLIER from .client import Client @@ -30,7 +30,7 @@ async def loop(self): async def send_punch( self, - punch: SiPunch, + punch_log: SiPunchLog, ) -> bool: def length(x: int): if x == 0: @@ -40,6 +40,7 @@ def length(x: int): return int(math.log10(x)) + 1 + punch = punch_log.punch now = datetime.now() data = { "control1": str(punch.code), @@ -49,7 +50,7 @@ def length(x: int): "sitime1": punch.time.strftime("%H:%M:%S"), "ms1": punch.time.strftime("%f")[:3], "roctime1": str(now)[:19], - "macaddr": punch.host_info.mac_address, + "macaddr": punch_log.host_info.mac_address, "1": "f", "length": str(118 + sum(map(length, [punch.code, punch.card, punch.mode]))), } diff --git a/python/yaroc/clients/sirap.py b/python/yaroc/clients/sirap.py index fc2aad8..cd7dc28 100644 --- a/python/yaroc/clients/sirap.py +++ b/python/yaroc/clients/sirap.py @@ -4,7 +4,7 @@ from typing import Literal from ..pb.status_pb2 import Status -from ..rs import SiPunch +from ..rs import SiPunchLog from .client import Client ENDIAN: Literal["little", "big"] = "little" @@ -61,7 +61,8 @@ def _serialize_punch(card_number: int, si_daytime: time, code: int) -> bytes: + SirapClient._time_to_bytes(si_daytime) ) - async def send_punch(self, punch: SiPunch) -> bool: + async def send_punch(self, punch_log: SiPunchLog) -> bool: + punch = punch_log.punch message = SirapClient._serialize_punch(punch.card, punch.time.time(), punch.code) return await self._send(message) diff --git a/python/yaroc/pb/utils.py b/python/yaroc/pb/utils.py index 8af94a6..ecd3470 100644 --- a/python/yaroc/pb/utils.py +++ b/python/yaroc/pb/utils.py @@ -3,7 +3,7 @@ from google.protobuf.timestamp_pb2 import Timestamp -from ..rs import SiPunch +from ..rs import SiPunchLog from .punches_pb2 import Punch from .status_pb2 import Coordinates @@ -14,9 +14,9 @@ def _datetime_to_prototime(time: datetime) -> Timestamp: return ret -def create_punch_proto(si_punch: SiPunch) -> Punch: +def create_punch_proto(punch_log: SiPunchLog) -> Punch: punch = Punch() - punch.raw = bytes(si_punch.raw) + punch.raw = bytes(punch_log.punch.raw) return punch diff --git a/python/yaroc/rs.pyi b/python/yaroc/rs.pyi index 856dc8c..492a3bf 100644 --- a/python/yaroc/rs.pyi +++ b/python/yaroc/rs.pyi @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timedelta from enum import IntEnum from typing import ClassVar as _ClassVar from typing import Dict @@ -17,17 +17,21 @@ class SiPunch(object): code: int time: datetime mode: int - host_info: HostInfo raw: bytes +class SiPunchLog(object): + punch: SiPunch + latency: timedelta + host_info: HostInfo + @staticmethod def new( card: int, code: int, time: datetime, mode: int, host_info: HostInfo, now: datetime - ) -> "SiPunch": ... + ) -> "SiPunchLog": ... @staticmethod - def from_raw(payload: bytes, host_info: HostInfo, now: datetime) -> "SiPunch": ... + def from_raw(payload: bytes, host_info: HostInfo, now: datetime) -> "SiPunchLog": ... @staticmethod - def from_msh_serial(payload: bytes) -> "SiPunch": ... + def from_msh_serial(payload: bytes) -> "SiPunchLog": ... class RaspberryModel(IntEnum): Unknown = 0 diff --git a/python/yaroc/sources/mqtt.py b/python/yaroc/sources/mqtt.py index beae64d..48defa1 100644 --- a/python/yaroc/sources/mqtt.py +++ b/python/yaroc/sources/mqtt.py @@ -13,7 +13,7 @@ from ..clients.client import ClientGroup from ..clients.mqtt import BROKER_PORT, BROKER_URL from ..pb.status_pb2 import Status as StatusProto -from ..rs import MessageHandler, SiPunch +from ..rs import MessageHandler, SiPunchLog from ..utils.status import StatusDrawer @@ -42,7 +42,7 @@ def _payload_to_bytes(payload: PayloadType) -> bytes: else: raise TypeError("Unexpected type of a message payload") - async def _process_punch(self, punch: SiPunch): + async def _process_punch(self, punch: SiPunchLog): logging.info(punch) await self.client_group.send_punch(punch) diff --git a/python/yaroc/sources/si.py b/python/yaroc/sources/si.py index 53330a6..ca83d51 100644 --- a/python/yaroc/sources/si.py +++ b/python/yaroc/sources/si.py @@ -16,7 +16,7 @@ from usbmonitor import USBMonitor from usbmonitor.attributes import DEVNAME, ID_MODEL_ID, ID_VENDOR_ID -from ..rs import HostInfo, SiPunch +from ..rs import HostInfo, SiPunchLog DEFAULT_TIMEOUT_MS = 3.0 START_MODE = 3 @@ -34,13 +34,14 @@ class SiWorker: def __init__(self): self._codes: set[int] = set() - async def process_punch(self, punch: SiPunch, queue: Queue): + async def process_punch(self, punch_log: SiPunchLog, queue: Queue): now = datetime.now().astimezone() + punch = punch_log.punch logging.info( f"{punch.card} punched {punch.code} at {punch.time:%H:%M:%S.%f}, received after " f"{(now-punch.time).total_seconds():3.2f}s" ) - await queue.put(punch) + await queue.put(punch_log) self._codes.add(punch.code) def __str__(self): @@ -58,7 +59,7 @@ def __init__(self, port: str, host_info: HostInfo): self.host_info = host_info self._finished = Event() - async def loop(self, queue: Queue[SiPunch]): + async def loop(self, queue: Queue[SiPunchLog]): successful = False for i in range(3): try: @@ -82,7 +83,7 @@ async def loop(self, queue: Queue[SiPunch]): await asyncio.sleep(1.0) continue now = datetime.now().astimezone() - punch = SiPunch.from_raw(data, self.host_info, now) + punch = SiPunchLog.from_raw(data, self.host_info, now) await self.process_punch(punch, queue) except serial.serialutil.SerialException as err: @@ -125,7 +126,7 @@ async def loop(self, queue: Queue, _status_queue): await asyncio.sleep(1.0) continue now = datetime.now().astimezone() - punch = SiPunch.from_raw(data, self.host_info, now) + punch = SiPunchLog.from_raw(data, self.host_info, now) await self.process_punch(punch, queue) except Exception as err: @@ -148,7 +149,7 @@ def extract_com(device_name: str) -> str: return match.groups()[0] - async def loop(self, queue: Queue[SiPunch], status_queue: Queue[DeviceEvent]): + async def loop(self, queue: Queue[SiPunchLog], status_queue: Queue[DeviceEvent]): self._loop = asyncio.get_event_loop() logging.info("Starting USB SportIdent device manager") self.monitor = USBMonitor(({ID_VENDOR_ID: "10c4"}, {ID_VENDOR_ID: "1a86"})) @@ -251,8 +252,8 @@ async def loop(self, queue: Queue, _status_queue): while True: time_start = time.time() now = datetime.now().astimezone() - punch = SiPunch.new(46283, 47, now, 18, HostInfo.new("fake", self.mac_addr), now) - await self.process_punch(punch, queue) + punch_log = SiPunchLog.new(46283, 47, now, 18, HostInfo.new("fake", self.mac_addr), now) + await self.process_punch(punch_log, queue) await asyncio.sleep(self._punch_interval - (time.time() - time_start)) @@ -266,7 +267,7 @@ class SiPunchManager: def __init__(self, workers: list[SiWorker]) -> None: self._si_workers: set[SiWorker] = set(workers) - self._queue: Queue[SiPunch] = Queue() + self._queue: Queue[SiPunchLog] = Queue() self._status_queue: Queue[DeviceEvent] = Queue() def __str__(self) -> str: @@ -280,7 +281,7 @@ async def loop(self): await asyncio.sleep(3) # Allow some time for an MQTT connection await asyncio.gather(*loops, return_exceptions=True) - async def punches(self) -> AsyncIterator[SiPunch]: + async def punches(self) -> AsyncIterator[SiPunchLog]: while True: yield await self._queue.get() diff --git a/src/message_handler.rs b/src/message_handler.rs index e6f244f..4487d4a 100644 --- a/src/message_handler.rs +++ b/src/message_handler.rs @@ -14,6 +14,7 @@ use chrono::DateTime; use crate::logs::{CellularLogMessage, HostInfo, MshLogMessage, PositionName}; use crate::protobufs::{Punches, Status}; use crate::punch::SiPunch; +use crate::punch::SiPunchLog; use crate::status::{CellularRocStatus, MeshtasticRocStatus, NodeInfo}; #[pyclass()] @@ -44,7 +45,7 @@ impl MessageHandler { } #[pyo3(name = "meshtastic_serial_msg")] - pub fn meshtastic_serial_msg_py(&mut self, payload: &[u8]) -> PyResult> { + pub fn meshtastic_serial_msg_py(&mut self, payload: &[u8]) -> PyResult> { Ok(self.msh_serial_msg(payload)?) } @@ -74,7 +75,7 @@ impl MessageHandler { } #[pyo3(name = "punches")] - pub fn punches_py(&mut self, payload: &[u8], mac_addr: &str) -> PyResult> { + pub fn punches_py(&mut self, payload: &[u8], mac_addr: &str) -> PyResult> { self.punches(payload, mac_addr).map_err(|err| err.into()) } @@ -110,7 +111,7 @@ impl MessageHandler { } impl MessageHandler { - pub fn punches(&mut self, payload: &[u8], mac_addr: &str) -> std::io::Result> { + pub fn punches(&mut self, payload: &[u8], mac_addr: &str) -> std::io::Result> { let punches = Punches::decode(payload)?; let host_info: HostInfo = HostInfo { name: self.resolve(mac_addr).to_owned(), @@ -122,7 +123,7 @@ impl MessageHandler { for punch in punches.punches { match Self::construct_punch(&punch.raw, &host_info, now) { Ok(si_punch) => { - status.punch(&si_punch); + status.punch(&si_punch.punch); result.push(si_punch); } Err(err) => { @@ -170,7 +171,7 @@ impl MessageHandler { } } - fn msh_serial_msg(&mut self, payload: &[u8]) -> std::io::Result> { + fn msh_serial_msg(&mut self, payload: &[u8]) -> std::io::Result> { let service_envelope = ServiceEnvelope::decode(payload).map_err(|e| std::io::Error::from(e))?; let packet = service_envelope.packet.ok_or(std::io::Error::new( @@ -180,7 +181,7 @@ impl MessageHandler { let mac_addr = format!("{:8x}", packet.from); const SERIAL_APP: i32 = PortNum::SerialApp as i32; let now = Local::now().fixed_offset(); - let host_info = HostInfo { + let mut host_info = HostInfo { name: self.resolve(&mac_addr).to_owned(), mac_address: mac_addr.clone(), }; @@ -189,7 +190,7 @@ impl MessageHandler { portnum: SERIAL_APP, payload, .. - })) => Ok(SiPunch::punches_from_payload(&payload[..], &host_info, now)), + })) => Ok(SiPunch::punches_from_payload(&payload[..])), _ => Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("{}: Encrypted message or wrong portnum", host_info.name), @@ -201,15 +202,18 @@ impl MessageHandler { .meshtastic_statuses .entry(mac_addr) .or_insert(MeshtasticRocStatus::new(host_info.name.clone())); + if let Some(mac_addr) = self.meshtastic_override_mac.as_ref() { + host_info.mac_address = mac_addr.clone(); + } for punch in punches.into_iter() { match punch { Ok(punch) => { - let mut punch = punch.clone(); status.punch(&punch); - if let Some(mac_addr) = self.meshtastic_override_mac.as_ref() { - punch.host_info.mac_address = mac_addr.clone(); - } - result.push(punch); + result.push(SiPunchLog { + latency: now - punch.time, + punch, + host_info: host_info.clone(), + }); } Err(err) => { error!("{}", err); @@ -224,9 +228,9 @@ impl MessageHandler { payload: &[u8], host_info: &HostInfo, now: DateTime, - ) -> std::io::Result { + ) -> std::io::Result { let length = payload.len(); - Ok(SiPunch::from_raw( + Ok(SiPunchLog::from_raw( payload.try_into().map_err(|_| { std::io::Error::new( std::io::ErrorKind::InvalidData, @@ -300,10 +304,10 @@ mod test_punch { let message = punches.encode_to_vec(); let mut handler = MessageHandler::new(HashMap::new(), None); - let punches = handler.punches(&message[..], "").unwrap(); - assert_eq!(punches.len(), 1); - assert_eq!(punches[0].code, 47); - assert_eq!(punches[0].card, 1715004); + let punch_logs = handler.punches(&message[..], "").unwrap(); + assert_eq!(punch_logs.len(), 1); + assert_eq!(punch_logs[0].punch.code, 47); + assert_eq!(punch_logs[0].punch.card, 1715004); } #[test] @@ -328,10 +332,10 @@ mod test_punch { let message = envelope.encode_to_vec(); let mut handler = MessageHandler::new(HashMap::new(), None); - let punches = handler.msh_serial_msg(&message[..]).unwrap(); - assert_eq!(punches.len(), 1); - assert_eq!(punches[0].code, 47); - assert_eq!(punches[0].card, 1715004); + let punch_logs = handler.msh_serial_msg(&message[..]).unwrap(); + assert_eq!(punch_logs.len(), 1); + assert_eq!(punch_logs[0].punch.code, 47); + assert_eq!(punch_logs[0].punch.card, 1715004); } #[test] diff --git a/src/punch.rs b/src/punch.rs index c00d806..f41f458 100644 --- a/src/punch.rs +++ b/src/punch.rs @@ -16,39 +16,37 @@ pub struct SiPunch { #[pyo3(get)] mode: u8, #[pyo3(get)] - pub host_info: HostInfo, - latency: Duration, - #[pyo3(get)] raw: [u8; 20], } +#[derive(Debug, Clone, PartialEq)] +#[pyclass] +pub struct SiPunchLog { + #[pyo3(get)] + pub punch: SiPunch, + pub latency: Duration, + #[pyo3(get)] + pub host_info: HostInfo, +} + const EARLY_SERIES_COMPLEMENT: u32 = 100_000 - (1 << 16); const BILLION_BY_256: u32 = 1_000_000_000 / 256; // An integer #[pymethods] impl SiPunch { #[staticmethod] - pub fn new( - card: u32, - code: u16, - time: DateTime, - mode: u8, - host_info: &HostInfo, - now: DateTime, - ) -> Self { + pub fn new(card: u32, code: u16, time: DateTime, mode: u8) -> Self { Self { card, code, time, - latency: now - time, mode, - host_info: host_info.clone(), raw: Self::punch_to_bytes(code, time, card, mode), } } #[staticmethod] - pub fn from_raw(payload: [u8; 20], host_info: &HostInfo, now: DateTime) -> Self { + pub fn from_raw(payload: [u8; 20]) -> Self { let data = &payload[4..19]; let code = u16::from_be_bytes([data[0] & 1, data[1]]); let mut card = u32::from_be_bytes(data[2..6].try_into().unwrap()) & 0xffffff; @@ -63,38 +61,24 @@ impl SiPunch { card, code, time: datetime, - latency: now - datetime, mode: data[4] & 0b1111, - host_info: host_info.clone(), raw: payload, } } - - pub fn __repr__(&self) -> String { - format!("{}", self) - } } impl SiPunch { - pub fn punches_from_payload( - payload: &[u8], - host_info: &HostInfo, - now: DateTime, - ) -> Vec> { + pub fn punches_from_payload(payload: &[u8]) -> Vec> { payload .chunks(20) .map(|chunk| { let length = chunk.len(); - Ok(Self::from_raw( - chunk.try_into().map_err(|_| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Wrong length of chunk={length}"), - ) - })?, - host_info, - now, - )) + Ok(Self::from_raw(chunk.try_into().map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Wrong length of chunk={length}"), + ) + })?)) }) .collect() } @@ -193,14 +177,47 @@ impl SiPunch { } } -impl fmt::Display for SiPunch { +#[pymethods] +impl SiPunchLog { + #[staticmethod] + pub fn new( + card: u32, + code: u16, + time: DateTime, + mode: u8, + host_info: &HostInfo, + now: DateTime, + ) -> Self { + Self { + punch: SiPunch::new(card, code, time, mode), + latency: now - time, + host_info: host_info.clone(), + } + } + + #[staticmethod] + pub fn from_raw(payload: [u8; 20], host_info: &HostInfo, now: DateTime) -> Self { + let punch = SiPunch::from_raw(payload); + Self { + latency: now - punch.time, + punch, + host_info: host_info.clone(), + } + } + + pub fn __repr__(&self) -> String { + format!("{}", self) + } +} + +impl fmt::Display for SiPunchLog { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{} {} punched {} ", - self.host_info.name, self.card, self.code + self.host_info.name, self.punch.card, self.punch.code )?; - write!(f, "at {}", self.time.format("%H:%M:%S.%3f"))?; + write!(f, "at {}", self.punch.time.format("%H:%M:%S.%3f"))?; let millis = self.latency.num_milliseconds() as f64 / 1000.0; write!(f, ", latency {:4.2}s", millis) } @@ -227,7 +244,10 @@ mod test_checksum { mod test_punch { use chrono::{prelude::*, Duration}; - use crate::{logs::HostInfo, punch::SiPunch}; + use crate::{ + logs::HostInfo, + punch::{SiPunch, SiPunchLog}, + }; #[test] fn test_card_series() { @@ -274,15 +294,11 @@ mod test_punch { .unwrap() .fixed_offset(); - let host_info = HostInfo { - name: "A".to_owned(), - mac_address: "a".to_owned(), - }; - let punch = SiPunch::new(1715004, 47, datetime, 2, &host_info, datetime); + let punch = SiPunch::new(1715004, 47, datetime, 2); let payload = b"\xff\x02\xd3\x0d\x00\x2f\x00\x1a\x2b\x3c\x08\x8c\xa3\xcb\x02\x00\x01\x50\xe3\x03\xff\x02"; - let punches = SiPunch::punches_from_payload(payload, &host_info, datetime); + let punches = SiPunch::punches_from_payload(payload); assert_eq!(punches.len(), 2); assert_eq!(*punches[0].as_ref().unwrap(), punch); assert_eq!( @@ -298,7 +314,7 @@ mod test_punch { name: "ROC1".to_owned(), mac_address: "abcdef123456".to_owned(), }; - let punch = SiPunch::new( + let punch = SiPunchLog::new( 46283, 47, time, diff --git a/src/python.rs b/src/python.rs index 0efb648..913818c 100644 --- a/src/python.rs +++ b/src/python.rs @@ -54,6 +54,7 @@ impl RaspberryModel { #[pymodule] pub fn rs(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/tests/test_si.py b/tests/test_si.py index c0d4345..b8e91bd 100644 --- a/tests/test_si.py +++ b/tests/test_si.py @@ -1,27 +1,28 @@ import unittest from datetime import datetime -from yaroc.rs import HostInfo, SiPunch +from yaroc.rs import HostInfo, SiPunchLog from yaroc.sources.si import UdevSiFactory class TestSportident(unittest.TestCase): def test_new(self): t = datetime(2023, 11, 23, 10, 0, 3, 792969).astimezone() - punch = SiPunch.new(1715004, 47, t, 2, HostInfo.new("name", "abcdef123456"), t) + punch_log = SiPunchLog.new(1715004, 47, t, 2, HostInfo.new("name", "abcdef123456"), t) self.assertEqual( - bytes(punch.raw), + bytes(punch_log.punch.raw), b"\xff\x02\xd3\r\x00\x2f\x00\x1a\x2b\x3c\x08\x8c\xa3\xcb\x02\x00\x01P\xe3\x03", ) def test_decode(self): bsf8_msg = b"\xff\x02\xd3\r\x00\x2f\x00\x1a\x2b\x3c\x18\x8c\xa3\xcb\x02\tPZ\x86\x03" now = datetime.now().astimezone() - punch = SiPunch.from_raw(bsf8_msg, HostInfo.new("name", "abcdef123456"), now) + punch_log = SiPunchLog.from_raw(bsf8_msg, HostInfo.new("name", "abcdef123456"), now) + punch = punch_log.punch self.assertEqual(punch.card, 1715004) self.assertEqual(punch.code, 47) self.assertEqual(punch.mode, 2) - self.assertEqual(punch.host_info.mac_address, "abcdef123456") + self.assertEqual(punch_log.host_info.mac_address, "abcdef123456") self.assertEqual(punch.time.weekday(), 3) self.assertEqual(punch.time.hour, 10) @@ -30,7 +31,8 @@ def test_decode(self): self.assertEqual(punch.time.microsecond, 792968) SIAC_msg = b"\xff\x02\xd3\r\x80\x02\x0f{\xc0\xd9\x011\n\xb9t\x00\x01\x8e\xcb\x03" - punch = SiPunch.from_raw(SIAC_msg, HostInfo.new("name", "abcdef123456"), now) + punch_log = SiPunchLog.from_raw(SIAC_msg, HostInfo.new("name", "abcdef123456"), now) + punch = punch_log.punch self.assertEqual(punch.card, 8110297) self.assertEqual(punch.code, 2) self.assertEqual(punch.mode, 4) # Finish