I developed a Hardware called Cloud Printer before, as shown in the picture and link below:
https://www.hackster.io/gavinchiong/chatgpt-recorder-printer-cloud-printer-413eaf
As a hardware, Cloud Printer can run different applications, such as the previously developed "ChatGPT Recorder & printer", which can independently complete ChatGPT question and answer, and store ChatGPT sessions in the internal TF card. The premise of developing that was that ChatGPT at that time could not store session content, but it is completely unnecessary now, because it is officially supported.
Today I will continue to use Cloud Printer as the hardware to develop a new application "AnyPrint''. It can transmit the text content on the PC to the Cloud Printer via WiFi or Ethernet, and then print it out.
Test videos:
I had imagined something like this:
Left-click to select a piece of text, and trigger the sending action through a new menu on the right mouse button, such as "Print this Text with Cloud Printer", and then send the selected text to Cloud Printer and print it;
But in the actual development process, I found that the right-click menu of each program is different, and the operation also needs to change the registry of the computer, which has certain difficulties and risks, so I changed it to trigger the sending action through the combination of the left and right keys.
Because of the assistance of ChatGPT, I have no experience in Python development and can develop an application as needed. Next, I will introduce my development process.
Step 1, Install and configure the Python runtime environmentAs I said earlier, I don't have any experience in Python development, so first I need to install the Python environment. I installed Python 3.11.5,
And set the system environment variables after installation.
C:\Users\GavinChiong\AppData\Local\Programs\Python\Python311\Scripts
C:\Users\GavinChiong\AppData\Local\Programs\Python\Python311
After that, you can edit and run Python code through Sublime.
This is the first version of the code generated by ChatGPT for me.
import socket
import threading
import pyautogui
import time
import win32gui
import win32con
import win32api
# TCP服务器配置
TCP_IP = '127.0.0.1'
TCP_PORT = 5005
# 监听鼠标事件的线程
class MouseThread(threading.Thread):
def __init__(self):
super().__init__()
self.running = True
def run(self):
while self.running:
if win32api.GetAsyncKeyState(win32con.VK_LBUTTON) & 0x8000: # 鼠标左键长按检测
text = pyautogui.hotkey('ctrl', 'c') # 模拟Ctrl+C复制选中文本
if text:
self.show_custom_menu()
time.sleep(0.1)
def show_custom_menu(self):
hwnd = win32gui.GetForegroundWindow() # 获取当前活动窗口句柄
hmenu = win32gui.GetSystemMenu(hwnd, False) # 获取系统菜单句柄
# 在系统菜单中添加自定义项
win32gui.AppendMenu(hmenu, win32con.MF_SEPARATOR, 0, '')
win32gui.AppendMenu(hmenu, win32con.MF_STRING, 1000, '发送选中文本')
# 监听并处理菜单项点击事件
cmd, _ = win32gui.TrackPopupMenuEx(
hmenu, win32con.TPM_LEFTALIGN | win32con.TPM_RETURNCMD, win32api.GetCursorPos(), hwnd, None
)
if cmd == 1000:
selected_text = pyautogui.paste() # 获取剪贴板中的文本
self.send_text(selected_text)
def send_text(self, text):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((TCP_IP, TCP_PORT))
s.sendall(text.encode())
except Exception as e:
print("Error sending text:", e)
if __name__ == "__main__":
mouse_thread = MouseThread()
mouse_thread.start()
input("Press Enter to stop...\n")
mouse_thread.running = False
mouse_thread.join()
In order to run this code, I need to install all the required library files. Python library file installation is realized through Pip. Open the run window through Win + R and enter the following command to install.
pip install win32gui
pip install pyautogui
After installing the library file, you can compile and run it in Sublime, as follows, change the format to Python, and then press Ctrl + B to run the code.
Then after a long period of debugging, the way of right-click menu is still not ideal, so I gave up and changed to the combination of left button and right button to trigger the operation.
This is the adjusted program, which can realize the basic functions, but there are still many wrong executions.
import socket
import threading
import time
import win32con
import win32api
import pyperclip
import tkinter as tk
from tkinter import messagebox
# TCP服务器配置
TCP_IP = '127.0.0.1'
TCP_PORT = 5005
# 鼠标事件监听线程
class MouseThread(threading.Thread):
def __init__(self):
super().__init__()
self.running = True
self.selected_text = ""
def run(self):
while self.running:
if win32api.GetAsyncKeyState(win32con.VK_LBUTTON) & 0x8000: # 鼠标左键长按检测
self.selected_text = self.get_selected_text()
time.sleep(0.1)
def get_selected_text(self):
return pyperclip.paste()
def send_text(self, text):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((TCP_IP, TCP_PORT))
s.sendall(text.encode())
except Exception as e:
print("Error sending text:", e)
def show_send_confirmation(selected_text):
root = tk.Tk()
root.withdraw() # 隐藏主窗口
answer = messagebox.askyesno("确认", f"是否要发送以下文本到服务器:\n\n{selected_text}")
return answer
if __name__ == "__main__":
mouse_thread = MouseThread()
mouse_thread.start()
try:
while True:
if mouse_thread.selected_text:
if win32api.GetAsyncKeyState(win32con.VK_RBUTTON) & 0x8000: # 检测右键点击
if show_send_confirmation(mouse_thread.selected_text):
mouse_thread.send_text(mouse_thread.selected_text)
mouse_thread.selected_text = ""
time.sleep(0.1)
except KeyboardInterrupt:
mouse_thread.running = False
mouse_thread.join()
I adjusted the trigger actions of the left and right keys according to my own ideas, because ChatGPT always can't understand my ideas,
There is also a very serious operation error. This code is executed in the background of the computer, and the sending operation is triggered by the left and right keys. In order to prevent misoperation, I originally wanted to right-click and hold for 3 seconds and then the system simulated the keyboard to send "Ctrl+C", but in fact, the right-click and long-press will automatically call out the right-click menu after 3 seconds. At this time, the copy text cannot be executed, that is, the action of "Ctrl+C".
So I changed the operation steps to long press the left button to select the text, and hold it for more than 1 second, then perform the copy operation "Ctrl+C", and then press and hold the right button for 1 second to trigger the sending action.
import socket
import threading
import time
import win32con
import pyautogui
import win32api
import pyperclip
import tkinter as tk
from tkinter import messagebox
# TCP server configuration
TCP_IP = '10.0.1.42'
TCP_PORT = 5005
class MouseThread(threading.Thread):
def __init__(self):
super().__init__()
self.running = True
self.left_click_start_time = 0
self.right_click_start_time = 0
self.left_hold = 0
self.right_click_pressed = False
self.left_click_pressed = False
self.selected_text = ""
def run(self):
while self.running:
self.check_left_click()
self.check_right_click()
time.sleep(0.1)
def check_right_click(self):
if self.left_hold is True:
if win32api.GetAsyncKeyState(win32con.VK_RBUTTON) & 0x8000:
if not self.right_click_pressed:
self.right_click_start_time = time.time()
self.right_click_pressed = True
print("Right Click")
else:
if self.right_click_pressed and time.time() - self.right_click_start_time >= 0.5:
print("Right Click Hold")
self.show_send_confirmation(self.selected_text)
self.left_hold = False
self.right_click_pressed = False
def check_left_click(self):
if win32api.GetAsyncKeyState(win32con.VK_LBUTTON) & 0x8000:
if not self.left_click_pressed:
self.left_click_start_time = time.time()
self.left_click_pressed = True
print("Left Click")
else:
if self.left_click_pressed and time.time() - self.left_click_start_time >= 1:
print("Left Click Hold")
self.left_hold = True
pyautogui.hotkey('ctrl', 'c') # Simulate Ctrl+C to copy selected text
time.sleep(0.1)
self.selected_text = pyperclip.paste() # Get text from clipboard
print(self.selected_text)
self.left_click_pressed = False
def send_text(self, text):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((TCP_IP, TCP_PORT))
s.sendall(text.encode())
self.selected_text = ""
except Exception as e:
print("Error sending text:", e)
def show_send_confirmation(self, selected_text):
root = tk.Tk()
root.withdraw()
answer = messagebox.askyesno("Confirmation", f"Do you want to send the following text to the server:\n\n{selected_text}")
if answer:
self.send_text(selected_text)
if __name__ == "__main__":
mouse_thread = MouseThread()
mouse_thread.start()
try:
while True:
pass
except KeyboardInterrupt:
mouse_thread.running = False
mouse_thread.join()
Then I added some content, such as changing the IP address and port number of Cloud Printer. The current operation logic is that when the sending operation is triggered, a pop-up window will pop up first, asking whether to print:
The current target IP and port will be displayed in the pop-up window, if it is printed, click "YES" or "Yes (Y)", if the target IP and port are wrong, click "NO" or "No (N)" and the setting interface will pop up. This interface can set the target IP and port number separately.
All Python code parts of AnyPrint have been completed, as follows:
import socket
import threading
import time
import win32con
import pyautogui
import win32api
import pyperclip
import tkinter as tk
from tkinter import messagebox, simpledialog
# TCP server configuration
TCP_IP = '10.0.1.42'
TCP_PORT = 5005
class SettingsWindow:
def __init__(self, parent):
self.parent = parent
self.window = tk.Toplevel(parent)
self.window.title("Settings")
self.window.geometry("300x150")
self.server_label = tk.Label(self.window, text="Enter new server address:")
self.server_label.pack()
self.server_entry = tk.Entry(self.window)
self.server_entry.pack()
self.port_label = tk.Label(self.window, text="Enter new port number:")
self.port_label.pack()
self.port_entry = tk.Entry(self.window)
self.port_entry.pack()
self.button = tk.Button(self.window, text="Confirm", command=self.update_settings)
self.button.pack()
def update_settings(self):
new_server = self.server_entry.get()
new_port = self.port_entry.get()
if new_server:
global TCP_IP
TCP_IP = new_server
if new_port:
global TCP_PORT
TCP_PORT = int(new_port)
self.window.destroy() # Close the settings window
class MouseThread(threading.Thread):
def __init__(self):
super().__init__()
self.running = True
self.left_click_start_time = 0
self.right_click_start_time = 0
self.left_hold = False
self.right_click_pressed = False
self.left_click_pressed = False
self.selected_text = ""
def run(self):
while self.running:
self.check_left_click()
self.check_right_click()
time.sleep(0.1)
def check_right_click(self):
if self.left_hold:
if win32api.GetAsyncKeyState(win32con.VK_RBUTTON) & 0x8000:
if not self.right_click_pressed:
self.right_click_start_time = time.time()
self.right_click_pressed = True
else:
if self.right_click_pressed and time.time() - self.right_click_start_time >= 0.5:
self.show_send_confirmation(self.selected_text)
self.left_hold = False
self.right_click_pressed = False
def check_left_click(self):
if win32api.GetAsyncKeyState(win32con.VK_LBUTTON) & 0x8000:
if not self.left_click_pressed:
self.left_click_start_time = time.time()
self.left_click_pressed = True
else:
if self.left_click_pressed and time.time() - self.left_click_start_time >= 1:
self.left_hold = True
pyautogui.hotkey('ctrl','c') # Simulate Ctrl+C to copy selected text
time.sleep(0.1)
self.selected_text = pyperclip.paste() # Get text from clipboard
self.left_click_pressed = False
def send_text(self, text):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((TCP_IP, TCP_PORT))
s.sendall(("TEXT"+text).encode())
self.selected_text = ""
except Exception as e:
print("Error sending text:", e)
def show_send_confirmation(self, selected_text):
root = tk.Tk()
root.withdraw()
answer = messagebox.askyesno("Confirmation", f"Do you want to send the following text to the server:\n\n{selected_text}")
if answer:
self.send_text(selected_text)
else:
settings_window = SettingsWindow(root)
root.wait_window(settings_window.window)
if __name__ == "__main__":
mouse_thread = MouseThread()
mouse_thread.start()
try:
while True:
pass
except KeyboardInterrupt:
mouse_thread.running = False
mouse_thread.join()
It should be noted that I added a "TEXT" prefix to the sent data to define the data type, which will be explained later.
Step3. Set to run this Python program automatically in the background when PC is power on.If you want to run this Python program automatically when the computer is turned on, you need to write a bat file and put it in the system startup folder.
bat file content:
@echo off
start /B "" "C:\Users\GavinChiong\AppData\Local\Programs\Python\Python311\python.exe" "D:\OneDrive\Arduino\Any_Print\python\AnyPrint.py"
exit
You need to change your Python.exe file path and Python program path.
Open the startup folder by entering "shell:startup" in the running window (Win + R), and put the generated bat file in it.
step4. Develop AnyPrint code through Arduino (WizFi360).1 Install library files and board support in the Arduino IDE;
Add "WIZnet WizFi360-EVB-PICO" support to Arduino IDE
Open up the Arduino IDE and go to File->Preferences.
In the dialog that pops up, enter the following URL in the "Additional Boards Manager URLs" field:
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
Search "WizFi360" and Install Board support by "Board Manager"
"Tools->Board:"***"-> Raspberry Pi RP2040 Boards(2.6.1) " Select “WIZnet WizFi360-EVB-PICO”.
Add “Adafruit_GFX” and “Adafruit_SH110X”, these library support the screen SH1106.
Add “LittleFS” and “SD”, these library support read and write file to the SD card;
Add “WS2812FX” in order to support WS2812;
Add “SoftwareSerial” in order to support thermal printer module;
4.2: Submit questions via the built-in web page;
Initialize serial port for WizFi360 module and change the baudrate to 2000000bps(MAX baudrate for wizfi360).
The first initialization is 115200, and then setting the baud rate (2000000) is added to the initialization part of the WiZfi360 library, and the second time is changed to 2000000bps.
// initialize serial for WizFi360 module
Serial2.setFIFOSize(4096);
Serial2.begin(2000000);
WiFi.init(&Serial2);
Check the wizfi360 Link status of wifi in the “void setup()”
// check for the presence of the shield
if (WiFi.status() == WL_NO_SHIELD) {
while (true);// don't continue
}
// attempt to connect to WiFi network
while ( status != WL_CONNECTED) {
status = WiFi.begin(ssid, pass);// Connect to WPA/WPA2 network
}
After connecting to the WiFi network, the OLED displays the IP address:
And the printer will also print out a QR code, which can quickly open the WEB connection of the printer by scanning it with the mobile phone.
In fact, this part of the program is simpler than the development of the ChatGPT Recorder & printer project. The code is simplified:
void loop(){
ws2812fx.service();
switch(currentState){
case do_webserver_index:
{
display.drawRect(28, 20, 28, 28, SH110X_WHITE);
display.display();
ws2812fx.setColor(GREEN);
client1 = server_http.available();
if (client1)
{
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client1.connected())
{
if (client1.available()) {
char c = client1.read();
data_String += c;
if(data_String.substring(0, 4) == "TEXT")
{
data_String = "";
Anyprint_text = "";
while(client1.available())
{
Anyprint_text += (char)client1.read();
}
Serial.println(Anyprint_text);
// close the connection:
delay(10);
client1.stop();
display.drawRect(28, 20, 28, 28, SH110X_BLACK);
//display.display();
currentState = do_print_work;
}
if (c == '\n' && currentLineIsBlank) {
dataStr = data_String.substring(0, 4);
Serial.println(dataStr);
if(dataStr == "GET ")
{
client1.print(html_page);
}
else if(dataStr == "POST")
{
data_String = "";
while(client1.available())
{
data_String += (char)client1.read();
}
//Serial.println(data_String);
dataStart = data_String.indexOf("printtext=") + strlen("printtext=");
Anyprint_text = data_String.substring(dataStart, data_String.length());
Serial.println(Anyprint_text);
Anyprint_text.replace("+", " ");
client1.print(html_page);
// close the connection:
delay(10);
client1.stop();
display.drawRect(28, 20, 28, 28, SH110X_BLACK);
//display.display();
currentState = do_print_work;
}
data_String = "";
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
}
else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
}
}
break;
case do_print_work:
{
display.drawRect(72, 20, 28, 28, SH110X_WHITE);
display.display();
ws2812fx.setColor(ORANGE);
InitializePrint();
Set_Align((byte)0x00);
Set_Bold(0x01);
Printer.print("--------------------------------");
Printer.print(Anyprint_text);
Printer.print("\r\n--------------------------------");
select_lines(2);
display.drawRect(72, 20, 28, 28, SH110X_BLACK);
delay(1000);
currentState = do_webserver_index;
}
break;
}
}
I use the same Socket (PORT:80) to run the HTTP Server and accept the print data transmitted by the PC, and then use the first 4 characters to define what type of data is, if it is "GET (space)", it is the browser to I send an HTML page request, if it is "POST", the browser sends me the print data, if it is "TEXT", it is the print data sent by the PC.
I modified part of the design of the HTML page as follows:
Enter text in the input box, click "Print" to print through Cloud Printer. The embedded HTML page code is as follows:
const char html_page[] PROGMEM = {
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n" // the connection will be closed after completion of the response
//"Refresh: 1\r\n" // refresh the page automatically every n sec
"\r\n"
"<!DOCTYPE HTML>\r\n"
"<html>\r\n"
"<head>\r\n"
"<meta charset=\"UTF-8\">\r\n"
"<title>Cloud Printer: Any Print </title>\r\n"
"<link rel=\"icon\" href=\"https://cdn.icon-icons.com/icons2/1090/PNG/512/printer_78349.png\" type=\"image/x-icon\">\r\n"
"</head>\r\n"
"<body>\r\n"
"<p style=\"text-align:center;\">\r\n"
"<img alt=\"ChatGPT\" src=\"https://cdn.icon-icons.com/icons2/1090/PNG/512/printer_78349.png\" height=\"200\" width=\"200\">\r\n"
"<h1 align=\"center\">Cloud Printer</h1>\r\n"
"<h1 align=\"center\">AnyPrint</h1>\r\n"
"<div style=\"text-align:center;vertical-align:middle;\">"
"<form action=\"/\" method=\"post\">"
"<textarea placeholder=\"Please enter the text which you want to print\" rows=\"4\" cols=\"50\" name=\"printtext\" style=\"height: 80px\" required></textarea><br><br>"
"<input type=\"submit\" value=\"Print\" style=\"height: 30px; width: 80px;\">"
"</form>"
"</div>"
"</p>\r\n"
"</body>\r\n"
"<html>\r\n"
};
Finish.
step5. Develop AnyPrint code through Arduino (W5100S)The code of w5100S is not much different from that of WizFi360. The main thing is to use Ethernet functions instead of WiFi functions. Because I only have SSD1306 OLED, the Ethernet Anyprint program uses SSD1306 as the display module.
This is the code for the different parts
/* Cloud_Printer:Any Print*/
#include <SPI.h>
#include <Ethernet.h>
#include "html_page.h"
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>
// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(10, 0, 1, 61);
EthernetClient client;
EthernetServer server_http(80);
void setup() {
Ethernet.init(17); // WIZnet W5100S-EVB-Pico
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
// print your WiFi shield's IP address
ip = Ethernet.localIP();
}
void loop(){
ws2812fx.service();
switch(currentState){
case do_webserver_index:
{
display.drawRect(28, 20, 28, 28, SSD1306_WHITE);
display.display();
ws2812fx.setColor(GREEN);
client = server_http.available();
if (client)
{
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client.connected())
{
if (client.available()) {
char c = client.read();
data_String += c;
if(data_String.substring(0, 4) == "TEXT")
{
data_String = "";
Anyprint_text = "";
while(client.available())
{
Anyprint_text += (char)client.read();
}
Serial.println(Anyprint_text);
// close the connection:
delay(10);
client.stop();
display.drawRect(28, 20, 28, 28, SSD1306_BLACK);
//display.display();
currentState = do_print_work;
}
if (c == '\n' && currentLineIsBlank) {
dataStr = data_String.substring(0, 4);
Serial.println(dataStr);
if(dataStr == "GET ")
{
client.print(html_page);
}
else if(dataStr == "POST")
{
data_String = "";
while(client.available())
{
data_String += (char)client.read();
}
//Serial.println(data_String);
dataStart = data_String.indexOf("printtext=") + strlen("printtext=");
Anyprint_text = data_String.substring(dataStart, data_String.length());
Serial.println(Anyprint_text);
Anyprint_text.replace("+", " ");
client.print(html_page);
// close the connection:
delay(10);
client.stop();
display.drawRect(28, 20, 28, 28, SSD1306_BLACK);
//display.display();
currentState = do_print_work;
}
data_String = "";
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
}
else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
}
}
break;
case do_print_work:
{
display.drawRect(72, 20, 28, 28, SSD1306_WHITE);
display.display();
ws2812fx.setColor(ORANGE);
InitializePrint();
Set_Align((byte)0x00);
Set_Bold(0x01);
Printer.print("--------------------------------");
Printer.print(Anyprint_text);
Printer.print("\r\n--------------------------------");
select_lines(2);
display.drawRect(72, 20, 28, 28, SSD1306_BLACK);
delay(1000);
currentState = do_webserver_index;
}
break;
}
}
Done.
Comments