Compare commits

...

12 Commits

Author SHA1 Message Date
3bb1134be2 fw: remove some smoltcp socket testing code 2023-06-16 16:33:39 -06:00
afa95aa32e fw: clean up some timer things in main 2023-06-16 16:33:07 -06:00
d4d119dc6e fw: remove vestiges of TCP logging attempt 2023-06-16 16:31:43 -06:00
800c6012f2 fw: formatting 2023-06-16 16:30:49 -06:00
db1ff6761d create python config prompt tool 2023-06-16 16:30:05 -06:00
ea94072e42 fw: reset packet parser after full packet received 2023-06-16 16:29:52 -06:00
1ab57fe316 tweaked container 2023-06-16 16:28:55 -06:00
30d6892477 add privileged flag to devcontainer, to flash from vscode 2023-06-16 14:45:25 -06:00
b57e02f852 Ignore build outputs in docker context to avoid permissions issues 2023-06-16 14:44:38 -06:00
7b53e8be2a fw: clean up and get command processing actually working
Still an issue where connecting to a socket the first time will time
out, unsure what the cause of that or where to start, but can ignore for
now and figure out more when I have more info later
2023-06-16 14:36:44 -06:00
c1e09e3927 update some documentation about the Dockerfile 2023-06-16 14:34:54 -06:00
b0040d8699 fw: re-enable socket (doesn't work though) 2023-06-03 18:23:06 -06:00
18 changed files with 314 additions and 200 deletions

View File

@ -0,0 +1,13 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
{
"name": "sonar_fpga",
"build": {
// Sets the run context to one level up instead of the .devcontainer folder.
"context": "..",
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
"dockerfile": "../Dockerfile"
},
"privileged": true
}

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
build/
firmware/target/
gateware/build/

View File

@ -3,7 +3,38 @@
All of this is assumed to be on a linux system. Eventually I'll probably make or
use some dockerfile or nix flake that has all this installed already.
## Toolchain Installation
The recommended dev flow if you want to make changes is to use VSCode with remote containers,
it's the easiest way to get the toolchain up and running, and to fix the issues encountered
with migen.
Recommended extensions:
- Pylance
- rust-analyzer
## Building with docker
Easiest way is to build and use the docker container:
```
docker build -r arvp_sonar:latest ./
# Privileged lets you access USB devices inside the container
docker run --privileged --rm -it -v /path/to/repository:/code arvp_sonar:latest
# These commands are run inside the container
cd /code/gateware
python3 litex_main.py --build # Build the bitstream
# To write to the flash storage
# TODO not sure how to use this command
ecpdap write
# To program the FPGA once
ecpdap program build/sonar/gateware/sonar.bit --freq 10M
```
This will generate a bitstream in `gateware/build/sonar
## Toolchain Manual Installation
### Trellis and FPGA toolchain

View File

@ -3,7 +3,7 @@ FROM ubuntu:22.04
WORKDIR /litex
# Install dependencies
RUN apt-get update && apt-get install -y wget curl python3 python3-pip git gcc-riscv64-linux-gnu ninja-build
RUN apt-get update && apt-get install -y wget curl python3 python3-pip python3-venv git gcc-riscv64-linux-gnu ninja-build
# TODO consolidate curl and wget
RUN pip3 install numpy matplotlib
@ -33,6 +33,8 @@ COPY migen.patch /litex/migen/migen.patch
RUN cd /litex/migen && git apply /litex/migen/migen.patch
RUN pip install --editable /litex/migen
# TODO remove unnecessary tools
# Delete package cache to keep size small
RUN apt-get clean

View File

@ -109,3 +109,8 @@ Do I have ecpdap installed in two places and one of them doesn't work maybe?
I do have two versions installed. one from oss-cad-suite and one by building it
manually.
### Pip install makes UNKNOWN package in docker
This is some weird conflict between pip's setuptools and the system setuptools,
to solve just set everything up in a venv.

View File

@ -20,3 +20,4 @@ features = ["medium-ethernet", "proto-ipv4", "socket-tcp", "socket-icmp", "defmt
[profile.release]
debug = true
opt-level = "s"

View File

@ -1,5 +1,5 @@
#!/usr/bin/sh
DEFMT_LOG=trace cargo build --release
DEFMT_LOG=debug cargo build --release
if [ $? -ne 0 ]
then
exit $?

View File

@ -1,7 +1,7 @@
MEMORY
{
RAM : ORIGIN = 0x10000000, LENGTH = 64K
FLASH : ORIGIN = 0x00000000, LENGTH = 80K
FLASH : ORIGIN = 0x00000000, LENGTH = 100K
}
REGION_ALIAS("REGION_TEXT", FLASH);

View File

@ -1,13 +1,16 @@
use smoltcp::socket::tcp::{Socket, State};
use crate::proto::{
serialize_response_error, serialize_response_value, PacketParser, ResponsePacket, Settings, ErrorCodes
serialize_response_error, serialize_response_value, ErrorCodes, PacketParser, ResponsePacket,
Settings,
};
pub struct CommandInterface {
parser: PacketParser,
}
const COMMAND_PORT: u16 = 2000;
impl CommandInterface {
pub fn new() -> Self {
Self {
@ -22,8 +25,16 @@ impl CommandInterface {
// provides a buffer of length 0 when there is nothing to receive, so we
// are good there. We just reset on any smoltcp errors.
// TODO should put packet parsing behind a fn with the smoltcp result, then on smoltcp error
// we reconfigure here instead of calling it 4 times
if !sock.is_open() {
defmt::debug!("Socket has been closed");
// Socket has been closed, some error has occured
sock.listen(COMMAND_PORT);
return;
}
if !sock.can_recv() {
return;
}
// Try and parse a packet
let res = sock.recv(|rx_buf| {
@ -32,47 +43,56 @@ impl CommandInterface {
(processed_bytes, res.ok())
});
defmt::debug!("Received data");
// Check for socket errors
let packet = match res {
// We got a packet! unwrap it
Ok(Some(packet)) => packet,
// No packet to process
// No packet to process, move on
Ok(None) => return,
// Any socket error means we just want to drop the connection and re-connect
Err(_) => {
Self::reestablish_socket(sock);
defmt::debug!("rx err");
sock.abort();
return;
}
};
defmt::debug!("Packet rx");
// Check that setting is valid
let setting = match Settings::try_from(packet.setting) {
Ok(v) => v,
Err(()) => {
// Write out an error packet
let result = sock.send(|tx_buf| {
// Write out an error packet, we'll just ignore errors
let _ = sock.send(|tx_buf| {
if tx_buf.len() < 8 {
// Just drop the packet if we don't have buffer available
return (0, ());
}
let response = serialize_response_error(packet.setting, ErrorCodes::InvalidSetting);
let response =
serialize_response_error(packet.setting, ErrorCodes::InvalidSetting);
&tx_buf[0..8].copy_from_slice(&response);
return (8, ());
});
if let Err(_) = result {
// TX error, close socket and reconnect
Self::reestablish_socket(sock);
}
return;
}
};
defmt::debug!(
"Valid packet: {:?}, is_write: {}, value: {}",
packet.setting,
packet.is_write,
packet.value
);
// TODO validate setting values
// TODO handle actually changing/getting settings in all the ways
// For temp testing, return values sent
match sock.send(|tx_buf| {
let res = sock.send(|tx_buf| {
if tx_buf.len() < 8 {
// Since this is a low-BW configuration socket, we assume we have space, and drop
// the response otherwise. If this is an issue, may re-think this and queue commands but
@ -82,16 +102,11 @@ impl CommandInterface {
let response = serialize_response_value(setting, packet.value);
&tx_buf[0..8].copy_from_slice(&response);
return (8, ());
}) {
Ok(()) => (),
Err(_) => {
Self::reestablish_socket(sock);
}
};
}
});
fn reestablish_socket(sock: &mut Socket) {
sock.abort();
sock.listen(2000);
if res.is_err() {
defmt::debug!("tx err");
sock.abort();
}
}
}

View File

@ -38,9 +38,6 @@ const SLOT_LEN: u32 = 2048;
use crate::{busy_wait, read_reg, write_reg};
use crate::uart::AmlibUart;
use core::fmt::Write;
pub struct LiteEthDevice {
csr_addr: u32,
ethmac_addr: u32,
@ -72,7 +69,10 @@ impl LiteEthDevice {
write_reg(csr_addr + ETHMAC_SRAM_WRITER_EV_ENABLE, 0u32);
// Return a new device
Some(Self { csr_addr, ethmac_addr })
Some(Self {
csr_addr,
ethmac_addr,
})
}
}
@ -199,13 +199,9 @@ impl smoltcp::phy::RxToken for LiteEthRxToken {
F: FnOnce(&mut [u8]) -> R,
{
// Read the slot number
let slot = unsafe {
read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_WRITER_SLOT)
};
let slot = unsafe { read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_WRITER_SLOT) };
// Read the available length
let len = unsafe {
read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_WRITER_LENGTH)
};
let len = unsafe { read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_WRITER_LENGTH) };
let rx_slot_addr: u32 = self.ethmac_addr + slot * SLOT_LEN;
let rx_slot: &mut [u8] =

View File

@ -1,45 +0,0 @@
//! Quick and dirty LiteX uart drier
const REG_RXTX: u32 = 0;
const REG_TXFULL: u32 = 0x4;
//const REG_RXEMPTY: u32 = 0x8;
use crate::{write_reg, read_reg};
use core::fmt::Write;
pub enum Error {
TxFull,
RxEmpty,
}
pub struct LiteXUart {
base_addr: u32,
}
impl LiteXUart {
pub fn new(base_addr: u32) -> Self{ Self {base_addr} }
pub fn try_put_char(&mut self, c: u8) -> Result<(), Error> {
unsafe {
if read_reg::<u32>(self.base_addr + REG_TXFULL) != 0 {
return Err(Error::TxFull);
}
write_reg::<u32>(self.base_addr + REG_RXTX, c as u32);
Ok(())
}
}
}
impl Write for LiteXUart {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for b in s.as_bytes() {
// It's okay to loop on this because we'll always clear the buffer
while let Err(Error::TxFull) = self.try_put_char(*b) {}
//self.try_put_char(*b);
}
Ok(())
}
}

View File

@ -1,8 +1,8 @@
use core::{fmt::Write, any::Any};
use core::{any::Any, fmt::Write};
use defmt;
use crate::uart::AmlibUart;
use crate::uart::LitexUart;
use core::arch::asm;
#[defmt::global_logger]
@ -22,17 +22,14 @@ unsafe impl defmt::Logger for DefmtLogger {
}
unsafe fn write(bytes: &[u8]) {
//static mut UART: Option<AmlibUart> = None;
//if UART.is_none() {
// UART = Some(AmlibUart::new(0x0200_0040));
//}
static mut UART: Option<LitexUart> = None;
if UART.is_none() {
UART = Some(LitexUart::new(0xf000_4000));
}
//let mut dev = UART.unwrap();
////writeln!(dev, "a").unwrap();
////writeln!(dev, "length: {}", bytes.len());
//for byte in bytes {
// while let Err(_) = dev.try_put_char(*byte) {}
//}
let mut dev = UART.unwrap();
for byte in bytes {
while let Err(_) = dev.try_put_char(*byte) {}
}
}
}

View File

@ -1,6 +1,5 @@
#![no_std]
#![no_main]
// TODO remove
#![allow(unused)]
@ -15,46 +14,29 @@ use core::{
use embedded_hal::prelude::{_embedded_hal_blocking_i2c_Read, _embedded_hal_blocking_i2c_Write};
use mcp4726::Status;
use riscv_rt::entry;
use smoltcp::socket::{Socket, self};
use smoltcp::socket::{self, Socket};
use smoltcp::time::Duration;
use smoltcp::wire::{IpAddress, Ipv4Address};
use smoltcp::{
iface::{SocketSet, SocketStorage},
time::Instant,
wire::HardwareAddress,
socket::tcp::Socket as TcpSocket,
socket::tcp::SocketBuffer,
time::Instant,
wire::HardwareAddress,
};
mod command_interface;
mod eth;
mod i2c;
mod mcp4726;
mod uart;
mod litex_uart;
mod logging;
mod mcp4726;
mod proto;
mod command_interface;
mod uart;
const MAC: [u8; 6] = [0xA0, 0xBB, 0xCC, 0xDD, 0xEE, 0xF0];
static mut SECONDS: u32 = 0;
/// External interrupt handler
#[export_name = "MachineExternal"]
fn external_interrupt_handler() {
let cause = riscv::register::mcause::read();
let mut uart = litex_uart::LiteXUart::new(0xf000_4000);
writeln!(uart, "mcause: {}", cause.bits());
if (cause.is_interrupt()) {
let mut uart = litex_uart::LiteXUart::new(0xf000_4000);
writeln!(uart, "mcause: {}", cause.code());
if cause.code() == 1 {
// TIMER0 event, we have reset so count another second
}
}
}
// use `main` as the entry point of this application
// `main` is not allowed to return
#[entry]
@ -69,13 +51,10 @@ fn main() -> ! {
// }
//};
let blink_period = 10_000_000u32;
let mut uart = litex_uart::LiteXUart::new(0xf000_4000);
writeln!(uart, "uart init");
// enable timer
let mut device = unsafe { eth::LiteEthDevice::try_init(0xf000_0800, 0x8000_0000).unwrap() };
writeln!(uart, "eth init");
use smoltcp::wire::{EthernetAddress, HardwareAddress};
let mut config = smoltcp::iface::Config::default();
@ -101,59 +80,60 @@ fn main() -> ! {
let mut socket_storage = [SocketStorage::EMPTY; 4];
let mut socket_set = SocketSet::new(&mut socket_storage[..]);
//let mut tx_storage = [0u8; 128];
//let mut rx_storage = [0u8; 128];
//let mut tx_buf = SocketBuffer::new(&mut tx_storage[..]);
//let mut rx_buf = SocketBuffer::new(&mut rx_storage[..]);
//let mut command_socket = socket_set.add(TcpSocket::new(tx_buf, rx_buf));
let mut tx_storage = [0u8; 64];
let mut rx_storage = [0u8; 64];
let mut tx_buf = SocketBuffer::new(&mut tx_storage[..]);
let mut rx_buf = SocketBuffer::new(&mut rx_storage[..]);
let mut command_socket = socket_set.add(TcpSocket::new(tx_buf, rx_buf));
// Set a keepalive on the socket to handle unexpected client disconnects
{
let mut sock = socket_set.get_mut::<TcpSocket>(command_socket);
// TODO these values are obscene, should fix the underlying bug
sock.set_keep_alive(Some(Duration::from_secs(2)));
sock.set_timeout(Some(Duration::from_secs(10)))
}
let mut last_blink: u32 = 0;
let mut toggle = false;
//defmt::info!("Done setup");
// Set up timer for polling events
unsafe {
//riscv::interrupt::enable();
//riscv::register::mie::set_mext();
//riscv::register::mie::set_msoft();
// Enable UART rx event for test
//write_reg(0xf000_4014, 1u32);
// Timer stuff
write_reg(0xf000_3808, 0u32); // Disable timer
write_reg(0xf000_3800, 0u32); // Set LOAD value
write_reg(0xf000_3804, 60_000_000u32); // Set RELOAD value
write_reg(0xf000_3808, 1u32); // Enable timer
// Enable timer event
//write_reg(0xf000_381c, 1u32);
}
//let mut cmd = command_interface::CommandInterface::new();
let mut cmd = command_interface::CommandInterface::new();
loop {
let now = millis();
if now - last_blink > 1000 {
// TODO the need for the second check screams something is unsound somewhere
if now - last_blink > 1000 && now > last_blink {
last_blink = now;
toggle = !toggle;
write_led(if toggle { 1 } else { 0 });
let val: u32 = unsafe {read_reg(0x8000_2000)};
writeln!(uart, "Sampler value: 0x{:08x}", val).unwrap();
}
if iface.poll(Instant::from_millis(now), &mut device, &mut socket_set) {
//cmd.run(socket_set.get_mut(command_socket));
let val: u32 = unsafe { read_reg(0x8000_2000) };
}
// TODO I think the timer might actually stop until the event is cleared? this may pose
// problems, might explain why moving this above the smoltcp stuff "broke" things
handle_timer_event();
iface.poll(Instant::from_millis(now), &mut device, &mut socket_set);
// TODO first connection to a socket takes a while to establish, and can time out, why?
cmd.run(socket_set.get_mut(command_socket));
}
}
fn handle_timer_event() {
unsafe {
if read_reg::<u32>(0xf000_3818) == 0 {
// No event yet, continue
return;
}
@ -161,19 +141,14 @@ fn handle_timer_event() {
write_reg(0xf000_3818, 1u32);
SECONDS += 1;
}
}
fn busy_wait(ms: u32) {
//let start = millis();
//while millis() - start < ms {
// unsafe {
// asm!("nop");
// }
//}
for i in 0..ms*20_000 {
unsafe {asm!("nop");}
let start = millis();
while millis() - start < ms {
unsafe {
asm!("nop");
}
}
}

View File

@ -194,11 +194,13 @@ impl PacketParser {
// Check CRC
7 => {
if self.crc.get_crc() == byte {
return Ok(CommandPacket {
let out = CommandPacket {
is_write: self.is_write,
setting: self.setting,
value: self.value,
});
};
self.reset();
return Ok(out);
} else {
self.reset();
return Err(Error::BadCrc);

View File

@ -2,18 +2,13 @@
use core::fmt::Write;
/// TODO repr(C) a register bank, and add instances
///
use core::ptr::{read_volatile, write_volatile};
// TODO these offsets may be wrong. I'm still unsure about CSR semantics
const REG_DIVISOR_OFFSET: u32 = 0;
const REG_SR_OFFSET: u32 = 2;
const REG_DR_OFFSET: u32 = 3;
const FLAG_SR_TX_FULL: u8 = 1 << 0;
const FLAG_SR_TX_EMPTY: u8 = 1 << 1;
const FLAG_SR_RX_FULL: u8 = 1 << 2;
const FLAG_SR_RX_EMPTY: u8 = 1 << 3;
const REG_RXTX: u32 = 0x00;
const REG_TXFULL: u32 = 0x04;
const REG_RXEMPTY: u32 = 0x08;
const REG_TXEMPTY: u32 = 0x18;
const REG_RXFULL: u32 = 0x1c;
pub enum Error {
TxFull,
@ -22,41 +17,43 @@ pub enum Error {
// Hacky derive, shouldn't exist but it's fine :tm:
#[derive(Copy, Clone)]
pub struct AmlibUart {
pub struct LitexUart {
base_addr: u32,
}
impl AmlibUart {
impl LitexUart {
pub fn new(base_addr: u32) -> Self {
Self { base_addr }
}
pub fn read_status(&mut self) -> u8 {
unsafe { read_volatile((self.base_addr + REG_SR_OFFSET) as *const u8) }
unsafe fn read_reg(&mut self, offset: u32) -> u32 {
read_volatile((self.base_addr + offset) as *const u32)
}
pub fn try_get_char(&mut self) -> Result<u8, Error> {
if self.read_status() & FLAG_SR_RX_EMPTY != 0 {
return Err(Error::RxEmpty);
}
unsafe {
if self.read_reg(REG_RXEMPTY) != 0 {
return Err(Error::RxEmpty);
}
unsafe { Ok(read_volatile((self.base_addr + REG_DR_OFFSET) as *const u8)) }
Ok(read_volatile((self.base_addr + REG_RXTX) as *mut u8))
}
}
pub fn try_put_char(&mut self, c: u8) -> Result<(), Error> {
if self.read_status() & FLAG_SR_TX_FULL != 0 {
return Err(Error::TxFull);
}
unsafe {
write_volatile((self.base_addr + REG_DR_OFFSET) as *mut u8, c);
if self.read_reg(REG_TXFULL) != 0 {
return Err(Error::TxFull);
}
write_volatile((self.base_addr + REG_RXTX) as *mut u8, c);
Ok(())
}
Ok(())
}
}
// Blocking implementation of write
impl Write for AmlibUart {
impl Write for LitexUart {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for b in s.as_bytes() {
// It's okay to loop on this because we'll always clear the buffer

View File

@ -8,7 +8,7 @@ authors = [
{ name = "David Lenfesty", email = "lenfesty@ualberta.ca" }
]
requires-python = ">=3.9"
dependencies = ["crc>=4.2"]
dependencies = ["crc>=4.2", "prompt-toolkit>=3.0"]
[project.scripts]
# Export CLI to configure FPGA

View File

@ -1,17 +1,134 @@
IP = "192.168.88.69"
PORT = 2000
import socket
from argparse import ArgumentParser
from prompt_toolkit import PromptSession
from prompt_toolkit.completion import NestedCompleter
from prompt_toolkit.validation import Validator, ValidationError
from .command_packets import CommandPacket, Settings, PacketParser
settings = {
"trigger_threshold": Settings.TriggerThreshold,
"trigger_period": Settings.TriggerPeriod,
"decay_value": Settings.DecayValue,
"decay_period": Settings.DecayPeriod,
"gain": Settings.Gain,
"center_frequency": Settings.CenterFreq,
"sampling_enabled": Settings.SamplingEnabled,
}
commands = {
"set": set(settings.keys()),
"get": set(settings.keys()),
"quit": None,
"help": None,
"?": None,
}
class CommandValidator(Validator):
def validate(self, document):
command = document.text.strip().split()
if len(command) == 0:
return
if command[0] not in commands.keys():
raise ValidationError(message="Unrecognized command")
if command[0] not in ["set", "get"]:
if len(command) > 1:
raise ValidationError(message="Too many arguments")
else:
if command[0] == "set":
num_args = 3
else:
num_args = 2
if len(command) < num_args:
raise ValidationError(message="Not enough arguments")
if len(command) > num_args:
raise ValidationError(message="Too many arguments")
if command[1] not in settings:
raise ValidationError(message="Unrecognized setting")
if num_args > 2:
try:
int(command[2])
except ValueError:
raise ValidationError(message="Setting value not an integer")
def print_help():
print("==== Sonar Configuration CLI ====")
print("Commands:")
print("\tset <setting> <value> - Sets the integer value of a setting")
print("\tget <setting> - gets the integer value of a setting")
print("\tquit - exit this prompt")
print("\thelp, ? - this help")
print()
print("Available Settings:")
for setting in settings.keys():
print(f"\t{setting}")
print()
def send_command(sock, cmd: CommandPacket):
"""Sends a command, and pretty-prints the response"""
sock.send(cmd.serialize())
data = sock.recv(16)
parser = PacketParser()
data, packet = parser.parse_bytearray(data)
if packet is not None:
print(packet)
else:
print("No response received! Must be a bug...")
def main():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((IP, PORT))
sock.send(CommandPacket(False, Settings.Gain, 125).serialize())
args = ArgumentParser(prog="sonar_config", description="Configuration utility for ARVP sonar")
args.add_argument("IP", type=str, help="IP of sonar system")
args.add_argument("--port", "-p", type=int, help="Port of configuration socket", default=2000)
args = args.parse_args()
data = sock.recv(1024)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((args.IP, args.port))
parser = PacketParser()
data, packet = parser.parse_bytearray(data)
if packet is not None:
print(packet)
# Start prompt
completer = NestedCompleter.from_nested_dict(commands)
validator = CommandValidator()
session = PromptSession("> ", completer=completer, validator=validator, validate_while_typing=True)
while True:
try:
command = session.prompt().strip().split()
# We assume the command is a valid command at this point,
# as long as the validator is doing it's job. Don't
# try and validate anything here!
if command[0] in ["help", "?"]:
print_help()
elif command[0] == "get":
send_command(sock, CommandPacket(False, settings[command[1]], 0))
elif command[0] == "set":
send_command(sock, CommandPacket(True, settings[command[1]], int(command[2])))
elif command[0] == "quit":
break
except KeyboardInterrupt:
# Ignore the current prompt, move on
pass
except EOFError:
# Ctrl-D exits
break
print("Disconnecting from socket...", end="")
# TODO this doesn't really close the socket the way I want it to...
# unsure if it's a FW issue, python issue, or weird docker interaction
sock.shutdown(socket.SHUT_RDWR)
sock.close()
print(" Goodbye!")

View File

@ -2,7 +2,7 @@ from crc import Calculator, Crc8
from typing import Optional, Tuple
from dataclasses import dataclass
from enum import IntEnum
from struct import pack
from struct import pack, unpack
PKT_START_SEQUENCE = [0xDE, 0xAD]
@ -103,6 +103,11 @@ class PacketParser:
if len(self.packet_data) < 7:
raise Exception("Invalid amount of packet data received!")
is_error = self.packet_data[2] & 0x80 != 0
setting = Settings(self.packet_data[2] & 0x7F)
value = unpack("<I", self.packet_data[3:7])
return ResponsePacket(is_error, setting, value)
def reset(self):
self.packet_data = bytearray()