Skip to content

Commit

Permalink
Merge pull request #5 from jpcurti/4-configure-serial-bridge-from-usb…
Browse files Browse the repository at this point in the history
…-to-usart2

4 configure serial bridge from usb to usart2
  • Loading branch information
jpcurti authored May 27, 2024
2 parents 1fadc59 + 2e08a2c commit e1e565d
Show file tree
Hide file tree
Showing 11 changed files with 717 additions and 2 deletions.
19 changes: 19 additions & 0 deletions docs/G-Codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,25 @@ loaded into the `printer.save_variables.variables` dict at startup and
can be used in gcode macros. The provided VALUE is parsed as a Python
literal.

### [serial_bridge]
The following command is enabled if a
[serial_bridge config section](Config_Reference.md#serial_bridge)
has been enabled.

#### SERIAL_BRIDGE_SEND
`SERIAL_BRIDGE_SEND [TEXT=<value>] [BRIDGE=<value>]`: This command will
send a serial message (TEXT) to the bridge specificed (BRIDGE).

#### SERIAL_BRIDGE_LIST_CONFIGS
`SERIAL_BRIDGE_LIST_CONFIGS`: This command will list the available
configurations reported by the MCU for use. This config should be used
when setting up a new [serial_bridge](Config_Reference.md#serial_bridge).

#### SERIAL_BRIDGE_LIST_BRIDGES
`SERIAL_BRIDGE_LIST_BRIDGES`: This command will list the available
bridges ready for use from the printer configuration
[serial_bridge](Config_Reference.md#serial_bridge).

### [screws_tilt_adjust]

The following commands are available when the
Expand Down
17 changes: 15 additions & 2 deletions klippy/extras/e3v3se_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ def __init__(self, config):
# register for key events
menu_keys.MenuKeys(config, self.key_event)

bridge = config.get('serial_bridge')

self.serial_bridge = self.printer.lookup_object(
'serial_bridge %s' %(bridge))
self.serial_bridge.register_callback(
self._handle_serial_bridge_response)

self._update_interval = 1
self._update_timer = self.reactor.register_timer(self._screen_update)

Expand All @@ -51,10 +58,16 @@ def get_encoder_state(self):

def encoder_has_data(self):
self.log("Key event")
return

def _handle_serial_bridge_response(self, data):
byte_debug = ' '.join(['0x{:02x}'.format(byte) for byte in data])
self.log("Received message: " + byte_debug)

def send_text(self, text):
self.serial_bridge.send_text(text)

def _screen_update(self, eventtime):
#self.log("Display update: ")
self.log("Display update")
return eventtime + self._update_interval

def _screen_init(self, eventtime):
Expand Down
192 changes: 192 additions & 0 deletions klippy/extras/serial_bridge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Support for "serial bridge"
#
# Copyright (C) 2019-2020 Kevin O'Connor <[email protected]>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging, re

QUERY_TIME = 0.2

class SerialBridge:
def __init__(self, config):
self.mcus = {}
self.configs = []
self.printer = config.get_printer()
self.gcode = self.printer.lookup_object("gcode")
self.gcode.register_command("SERIAL_BRIDGE_SEND",
self.cmd_SERIAL_BRIDGE_SEND,
desc="Send a message to a uart bridge")
self.gcode.register_command("SERIAL_BRIDGE_LIST_CONFIGS",
self.cmd_SERIAL_BRIDGE_LIST_CONFIGS,
desc="List Available serial configs")
self.gcode.register_command("SERIAL_BRIDGE_LIST_BRIDGES",
self.cmd_SERIAL_BRIDGE_LIST_BRIDGES,
desc="List current bridges")
self.printer.register_event_handler("klippy:ready", self.handle_ready)
self.printer.register_event_handler("klippy:disconnect",
self.handle_disconnect)
self.bridges = {}

def handle_ready(self):
self._ready = True

self.mcus = self.printer.lookup_objects('mcu')

self.configs = []
for n, mcu in self.mcus:
constants = mcu.get_constants()
configs= (
["%s=%s" % (k, v) for k,v in constants.items() \
if k.startswith("SERIAL_BRIDGE_CONFIG")])

self.configs.extend(configs)
logging.info("Serial bridge: available configs for %s: " % (n)
+ ", ".join(configs))

def handle_disconnect(self):
pass

def setup_bridge(self, bridge):
self.bridges[bridge.name.split()[-1]] = bridge

def cmd_SERIAL_BRIDGE_LIST_CONFIGS(self, gcmd):
gcmd.respond_info((", ".join(self.configs)))

def cmd_SERIAL_BRIDGE_LIST_BRIDGES(self, gcmd):
gcmd.respond_info((", ".join(self.bridges.keys())))

def cmd_SERIAL_BRIDGE_SEND(self, gcmd):
text = gcmd.get("TEXT")
bridge = gcmd.get("BRIDGE")

if not bridge:
gcmd.respond_info("BRIDGE is required")
return

if bridge not in self.bridges:
gcmd.respond_info("BRIDGE not found")
return

self.bridges[bridge].send_serial(
self.perform_replacement(gcmd.get("TEXT")))

def get_configs(self):
return self.configs

def perform_replacement(self, input_string):
# Find all occurrences of "\x" followed by two hexadecimal digits
hex_matches = re.finditer(r'\\x([0-9a-fA-F]{2})', input_string)

# Replace each matched hex sequence with its corresponding bytes
replaced_bytes = bytearray()
last_index = 0

for match in hex_matches:
hex_value = match.group(1)
byte_value = bytearray.fromhex(hex_value)
replaced_bytes.extend(byte_value)
last_index = match.end()

replaced_bytes.extend(input_string[last_index:].encode('utf-8'))

return replaced_bytes

class PrinterSerialBridge:
def __init__(self, config):
self.callbacks = []
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.eol = config.get('eol', default='\n')
self._ready = False
self.baud = config.getint("baud", 115200)
self.serial_config = config.getint("config", 4)
self._logging = config.getboolean("logging", False)

self.reactor = self.printer.get_reactor()
self.printer.register_event_handler("klippy:ready", self.handle_ready)
self.printer.register_event_handler("klippy:disconnect",
self.handle_disconnect)

ppins = self.printer.lookup_object("pins")
pin_params = ppins.lookup_pin(config.get("tx_pin"))
rx_pin_params = ppins.lookup_pin(config.get("rx_pin"))
self.mcu = pin_params['chip']
self.oid = self.mcu.create_oid()
self.mcu.register_config_callback(self.build_config)

self.input_buffer = ""

self.serial_bridge = self.printer.load_object(config, "serial_bridge")
self.serial_bridge.setup_bridge(self)

def register_callback(self, callback):
self.callbacks.append(callback)

def chunkstring(self, msg, length):
return (msg[0+i:length+i] for i in range(0, len(msg), length))

def send_text(self, msg):
self.send_serial(msg.encode('utf-8'))

def send_bytes(self, msg):
byte_debug = ' '.join(['0x{:02x}'.format(byte) for byte in msg])
self.log("Sending bytes: " + byte_debug)
self.serial_bridge_send_cmd.send([self.oid, msg, 4])


def send_serial(self, msg):
if not self._ready:
self.warn("Can't send message in a disconnected state")
return

chunks = self.chunkstring(
msg + self.serial_bridge.perform_replacement(self.eol), 40)
for chunk in chunks:
byte_debug = ' '.join(['0x{:02x}'.format(byte) for byte in chunk])
self.log("Sending message: " + byte_debug)
self.serial_bridge_send_cmd.send([self.oid, chunk, 4])

def build_config(self):
rest_ticks = self.mcu.seconds_to_clock(QUERY_TIME)
clock = self.mcu.get_query_slot(self.oid)
self.mcu.add_config_cmd(
"command_config_serial_bridge oid=%d clock=%d rest_ticks=%d "\
"config=%d baud=%d" % (
self.oid, clock, rest_ticks, self.serial_config, self.baud
))

cmd_queue = self.mcu.alloc_command_queue()

self.mcu.register_response(self._handle_serial_bridge_response,
"serial_bridge_response", self.oid)
self.serial_bridge_send_cmd = self.mcu.lookup_command(
"serial_bridge_send oid=%c text=%*s",
cq=cmd_queue)

def _handle_serial_bridge_response(self, params):
data = params["text"]

data = bytearray(data)

for callback in self.callbacks:
callback(data)

def handle_ready(self):
self.log("Ready")
self._ready = True

def handle_disconnect(self):
self._ready = False

def log(self, msg, *args, **kwargs):
if self._logging:
logging.info("SERIAL BRIDGE %s: " % (self.name) + str(msg) )

def warn(self, msg, *args, **kwargs):
logging.warning("SERIAL BRIDGE %s: " % (self.name) + str(msg))

def load_config(config):
return SerialBridge(config)

def load_config_prefix(config):
return PrinterSerialBridge(config)
1 change: 1 addition & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Main code build rules

src-y += sched.c command.c basecmd.c debugcmds.c
src-$(CONFIG_SERIAL_BRIDGE) += serial_bridge.c
src-$(CONFIG_HAVE_GPIO) += initial_pins.c gpiocmds.c stepper.c endstop.c \
trsync.c dirzctl.c hx711s.c
src-$(CONFIG_HAVE_GPIO_ADC) += adccmds.c
Expand Down
119 changes: 119 additions & 0 deletions src/generic/serial_bridge_irq.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Generic interrupt based serial uart helper code
//
// Copyright (C) 2016-2018 Kevin O'Connor <[email protected]>
//
// This file may be distributed under the terms of the GNU GPLv3 license.

#include <string.h> // memmove
#include "autoconf.h" // CONFIG_SERIAL_BAUD
#include "board/io.h" // readb
#include "board/irq.h" // irq_save
#include "board/misc.h" // console_sendf
#include "board/pgm.h" // READP
#include "command.h" // DECL_CONSTANT
#include "sched.h" // sched_wake_tasks
#include "serial_bridge_irq.h" // serial_enable_tx_irq
#include "board/serial_bridge.h" //SERIAL_BRIDGE_CNT

static uint8_t receive_bridge_buf
[SERIAL_BRIDGE_CNT][SERIAL_BRIDGE_RX_BUFF_SIZE],
receive_bridge_pos[SERIAL_BRIDGE_CNT];
static uint8_t transmit_bridge_buf
[SERIAL_BRIDGE_CNT][SERIAL_BRIDGE_RX_BUFF_SIZE],
transmit_bridge_pos[SERIAL_BRIDGE_CNT], transmit_bridge_max[SERIAL_BRIDGE_CNT];

void serial_bridge_rx_byte(uint_fast8_t data, uint8_t usart_index) {
if (receive_bridge_pos[usart_index] >= SERIAL_BRIDGE_RX_BUFF_SIZE)
// Serial overflow - ignore
return;
sched_wake_tasks();
receive_bridge_buf[usart_index][receive_bridge_pos[usart_index]++] = data;
}

int serial_bridge_get_tx_byte(uint8_t *pdata, uint8_t usart_index) {
if (transmit_bridge_pos[usart_index] >= transmit_bridge_max[usart_index])
return -1;
*pdata =
transmit_bridge_buf[usart_index][transmit_bridge_pos[usart_index]++];
return 0;
}

void
serial_bridge_send(uint8_t* data, uint_fast8_t size, uint8_t config)
{
uint8_t* usart_index_ptr
= serial_bridge_get_uart_index_from_config(config);

if(usart_index_ptr == NULL){
return;
}

uint8_t usart_index = *usart_index_ptr;

// Verify space for message
uint_fast8_t tpos =
readb(&transmit_bridge_pos[usart_index]),
tmax = readb(&transmit_bridge_max[usart_index]);

if (tpos >= tmax) {
tpos = tmax = 0;
writeb(&transmit_bridge_max[usart_index], 0);
writeb(&transmit_bridge_pos[usart_index], 0);
}

if (tmax + size > sizeof(transmit_bridge_buf[usart_index])) {
if (tmax + size - tpos > sizeof(transmit_bridge_buf[usart_index]))
// Not enough space for message
return;
// Disable TX irq and move usart_index
writeb(&transmit_bridge_max[usart_index], 0);
tpos = readb(&transmit_bridge_pos[usart_index]);
tmax -= tpos;
memmove(&transmit_bridge_buf[usart_index][0],
&transmit_bridge_buf[usart_index][tpos], tmax);
writeb(&transmit_bridge_pos[usart_index], 0);
writeb(&transmit_bridge_max[usart_index], tmax);
serial_bridge_enable_tx_irq(usart_index);
}

// Generate message
uint8_t *buf = &transmit_bridge_buf[usart_index][tmax];
memcpy(buf, data, size);

// Start message transmit
writeb(&transmit_bridge_max[usart_index], tmax + size);
serial_bridge_enable_tx_irq(usart_index);
}

// Remove from the receive buffer the given number of bytes
uint8_t
serial_bridge_get_data(uint8_t* data, uint8_t config)
{
uint8_t* usart_index_ptr
= serial_bridge_get_uart_index_from_config(config);

if(usart_index_ptr == NULL){
return 0;
}

uint8_t usart_index = *usart_index_ptr;

for (;;) {
uint_fast8_t rpos = readb(&receive_bridge_pos[usart_index]);
if (!rpos)
return 0;

uint8_t *buf = receive_bridge_buf[usart_index];
memcpy(data, buf, rpos);
irqstatus_t flag = irq_save();
if (rpos != readb(&receive_bridge_pos[usart_index])) {
// Raced with irq handler - retry
irq_restore(flag);
continue;
}
receive_bridge_pos[usart_index] = 0;
irq_restore(flag);

return rpos;
}
}
Loading

0 comments on commit e1e565d

Please sign in to comment.