Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows Compatibility #50

Merged
merged 8 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/hio/base/filing.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,15 @@ class Filer(hioing.Mixin):
stat.S_IWUSR Owner has write permission.
stat.S_IXUSR Owner has execute permission.
"""
HeadDirPath = "/usr/local/var" # default in /usr/local/var
HeadDirPath = os.path.join(os.path.sep, 'usr', 'local', 'var') # default in /usr/local/var
TailDirPath = "hio"
CleanTailDirPath = "hio/clean"
AltHeadDirPath = "~" # put in ~ as fallback when desired not permitted
CleanTailDirPath = os.path.join("hio", "clean")


AltHeadDirPath = os.path.expanduser("~") # put in ~ as fallback when desired not permitted
AltTailDirPath = ".hio"
AltCleanTailDirPath = ".hio/clean"
TempHeadDir = "/tmp"
AltCleanTailDirPath = os.path.join(".hio", "clean")
TempHeadDir = os.path.join(os.path.sep, "tmp")
TempPrefix = "hio_"
TempSuffix = "_test"
Perm = stat.S_ISVTX | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR # 0o1700==960
Expand Down
5 changes: 4 additions & 1 deletion src/hio/core/coring.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import subprocess
import socket
import platform

#import netifaces # netifaces2

Expand All @@ -20,6 +21,8 @@ def normalizeHost(host):
"""
if host == "":
host = "0.0.0.0"
if platform.system() == "Windows":
host = "127.0.0.1"

try: # try ipv4
info = socket.getaddrinfo(host,
Expand All @@ -28,7 +31,7 @@ def normalizeHost(host):
socket.SOCK_DGRAM,
socket.IPPROTO_IP, 0)
except socket.gaierror as ex: # try ipv6
if host in ("", "0.0.0.0"):
if host in ("", "0.0.0.0", "127.0.0.1"):
host = "::"

info = socket.getaddrinfo(host,
Expand Down
161 changes: 131 additions & 30 deletions src/hio/core/serial/serialing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import sys
import os
import errno
import platform
from collections import deque


Expand Down Expand Up @@ -87,12 +88,30 @@ def open(self, port=''):

To debug use os.isatty(fd) which returns True if the file descriptor
fd is open and connected to a tty-like device, else False.

On UNIX/macOS systems uses os.ctermid() to get console
On Windows systems uses console directly via msvcrt
"""
if not port:
port = os.ctermid() # default to console
system = platform.system()
if system == 'Windows':
# Windows doesn't need a specific port for console access via msvcrt
port = None
else:
# Unix/macOS use ctermid
port = os.ctermid() # default to console

try:
self.fd = os.open(port, os.O_NONBLOCK | os.O_RDWR | os.O_NOCTTY)
if platform.system() == 'Windows':
# Windows handling through msvcrt
import msvcrt
# For Windows, we'll use stdin/stdout file descriptors
# and rely on msvcrt for non-blocking operations
self.fd = 0 # Use 0 as a placeholder value for Windows console
# No specific open operation needed for Windows console via msvcrt
else:
# Unix/macOS handling
self.fd = os.open(port, os.O_NONBLOCK | os.O_RDWR | os.O_NOCTTY)
except OSError as ex:
logger.error("Error opening console serial port, %s\n", ex)
return False
Expand All @@ -115,7 +134,7 @@ def close(self):
"""
if self.fd:
os.close(self.fd)
self.fd = None
self.fd = None
del self.rxbs[:]
self.opened = False

Expand All @@ -124,7 +143,16 @@ def put(self, data = b'\n'):
"""
Writes data bytes to console and return number of bytes from data written.
"""
return (os.write(self.fd, data)) # returns number of bytes written
if platform.system() == 'Windows':
# Windows-specific console output
import msvcrt
# Write bytes to stdout
for b in data:
msvcrt.putch(bytes([b]))
return len(data)
else:
# Unix/macOS
return os.write(self.fd, data) # returns number of bytes written


def get(self, bs=None):
Expand All @@ -142,8 +170,19 @@ def get(self, bs=None):
"""
bs = bs if bs is not None else self.bs
line = bytearray()

try:
self.rxbs.extend(os.read(self.fd, bs))
if platform.system() == 'Windows':
# Windows-specific non-blocking read using msvcrt
import msvcrt
# Check if any keys are available
while msvcrt.kbhit():
# Read a character and add it to our buffer
char = msvcrt.getch()
self.rxbs.extend(char)
else:
# Unix/macOS non-blocking read
self.rxbs.extend(os.read(self.fd, bs))
except OSError as ex1: # if no chars available generates exception
# ex.args[0] == ex.errno for better os compatibility
# the value of a given errno.XXXXX may be different on each os
Expand All @@ -156,11 +195,14 @@ def get(self, bs=None):
" '%s'\n", self.fd, ex1)
raise # re-raise exception ex1


else:
if (idx := self.rxbs.find(ord(b'\n'))) != -1:
line.extend(self.rxbs[:idx]) # copy all but newline
del self.rxbs[:idx+1] # delete including newline
# Process any complete lines in the buffer
if (idx := self.rxbs.find(ord(b'\n'))) != -1:
line.extend(self.rxbs[:idx]) # copy all but newline
del self.rxbs[:idx+1] # delete including newline
elif platform.system() == 'Windows' and (idx := self.rxbs.find(ord(b'\r'))) != -1:
# On Windows, handle CR as line terminator too
line.extend(self.rxbs[:idx]) # copy all but CR
del self.rxbs[:idx+1] # delete including CR

return line

Expand Down Expand Up @@ -296,7 +338,6 @@ class Device():
Use instance methods get & put to read & write to serial device
Needs os module
"""

def __init__(self, port=None, speed=9600, bs=1024):
"""
Initialization method for instance.
Expand All @@ -306,7 +347,15 @@ def __init__(self, port=None, speed=9600, bs=1024):
bs = buffer size for reads
"""
self.fd = None # serial device port file descriptor, must be opened first
self.port = port or os.ctermid() #default to console
self.port = port

if not self.port:
system = platform.system()
if system == 'Windows':
self.port = 'COM1' # Default Windows serial port
else:
self.port = os.ctermid() # Default to console on Unix/macOS

self.speed = speed or 9600
self.bs = bs or 1024
self.opened = False
Expand Down Expand Up @@ -372,9 +421,27 @@ def setraw(fd, when=TCSAFLUSH):
if bs is not None:
self.bs = bs

self.fd = os.open(self.port, os.O_NONBLOCK | os.O_RDWR | os.O_NOCTTY)

system = platform.system()

if system == 'Windows':
try:
# Try to use pyserial for COM ports on Windows for better handling
import serial
self._serial = serial.Serial(port=self.port,
baudrate=self.speed,
timeout=0)
# Placeholder for fd
self.fd = 0
except ImportError:
# Fall back to basic file operations if pyserial is not available
self.fd = os.open(self.port, os.O_RDWR | os.O_BINARY)
import msvcrt
msvcrt.setmode(self.fd, os.O_BINARY)
self._serial = None
else:
# Unix/macOS handling
self.fd = os.open(self.port, os.O_NONBLOCK | os.O_RDWR | os.O_NOCTTY)
self._serial = None

if (system == 'Darwin') or (system == 'Linux'): # use termios to set values
import termios
Expand Down Expand Up @@ -419,12 +486,17 @@ def setraw(fd, when=TCSAFLUSH):
def close(self):
"""
Closes fd.

"""
if self.fd:
if self._serial:
self._serial.close()
self._serial = None

if self.fd and platform.system() != 'Windows' or not self._serial:
# Close fd if not Windows or if we didn't use pyserial
os.close(self.fd)
self.fd = None
self.opened = False

self.fd = None
self.opened = False


def receive(self):
Expand All @@ -435,13 +507,28 @@ def receive(self):
"""
data = b''
try:
data = os.read(self.fd, self.bs) #if no chars available generates exception
if platform.system() == 'Windows':
if self._serial:
# Use pyserial if available
data = self._serial.read(self.bs)
else:
# Fall back to msvcrt for console
import msvcrt
if msvcrt.kbhit():
char = msvcrt.getch()
data = char
else:
data = os.read(self.fd, self.bs)
else:
# Unix/macOS read
data = os.read(self.fd, self.bs) #if no chars available generates exception
except OSError as ex1: # ex1 is the target instance of the exception
if ex1.errno == errno.EAGAIN: #BSD 35, Linux 11
# BSD 35, Linux 11
if ex1.errno == errno.EAGAIN or (platform.system() == 'Windows' and ex1.errno == errno.EWOULDBLOCK):
pass #No characters available
else:
logger.error("Error: Receive on Device '%s'."
" '%s'\n", self.port, ex)
" '%s'\n", self.port, ex1)
raise #re raise exception ex1

return data
Expand All @@ -452,13 +539,18 @@ def send(self, data=b'\n'):
Returns number of bytes sent
"""
try:
count = os.write(self.fd, data)
if platform.system() == 'Windows' and self._serial:
# Use pyserial if available
count = self._serial.write(data)
else:
count = os.write(self.fd, data)
except OSError as ex1: # ex1 is the target instance of the exception
if ex1.errno == errno.EAGAIN: # BSD 35, Linux 11
# BSD 35, Linux 11
if ex1.errno == errno.EAGAIN or (platform.system() == 'Windows' and ex1.errno == errno.EWOULDBLOCK):
count = 0 # buffer full can't write
else:
logger.error("Error: Send on Device '%s'."
" '%s'\n", self.port, ex)
" '%s'\n", self.port, ex1)
raise #re raise exception ex1

return count
Expand All @@ -473,7 +565,6 @@ class Serial():
Use instance methods get & put to read & write to serial device
Needs os module
"""

def __init__(self, port=None, speed=9600, bs=1024):
"""
Initialization method for instance.
Expand All @@ -485,7 +576,15 @@ def __init__(self, port=None, speed=9600, bs=1024):

"""
self.serial = None # Serial instance
self.port = port or os.ctermid() #default to console
self.port = port

if not self.port:
system = platform.system()
if system == 'Windows':
self.port = 'COM1' # Default Windows serial port
else:
self.port = os.ctermid() # Default to console on Unix/macOS

self.speed = speed or 9600
self.bs = bs or 1024
self.opened = False
Expand Down Expand Up @@ -540,11 +639,12 @@ def receive(self):
try:
data = self.serial.read(self.bs) #if no chars available generates exception
except OSError as ex1: # ex1 is the target instance of the exception
if ex1.errno == errno.EAGAIN: #BSD 35, Linux 11
# BSD 35, Linux 11
if ex1.errno == errno.EAGAIN or (platform.system() == 'Windows' and ex1.errno == errno.EWOULDBLOCK):
pass #No characters available
else:
logger.error("Error: Receive on Serial '%s'."
" '%s'\n", self.port, ex)
" '%s'\n", self.port, ex1)
raise #re raise exception ex1

return data
Expand All @@ -557,11 +657,12 @@ def send(self, data=b'\n'):
try:
count = self.serial.write(data)
except OSError as ex1: # ex1 is the target instance of the exception
if ex1.errno == errno.EAGAIN: #BSD 35, Linux 11
# BSD 35, Linux 11
if ex1.errno == errno.EAGAIN or (platform.system() == 'Windows' and ex1.errno == errno.EWOULDBLOCK):
count = 0 # buffer full can't write
else:
logger.error("Error: Send on Serial '%s'."
" '%s'\n", self.port, ex)
" '%s'\n", self.port, ex1)
raise #re raise exception ex1

return count
Expand Down
6 changes: 4 additions & 2 deletions src/hio/core/wiring.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@ class WireLog():

"""
Prefix = "hio"
HeadDirPath = "/usr/local/var" # default in /usr/local/var

HeadDirPath = os.path.join(os.path.sep, 'usr', 'local', 'var') # default in /usr/local/var

TailDirPath = "wirelogs"
AltHeadDirPath = "~" # put in ~ as fallback when desired dir not permitted
TempHeadDir = "/tmp"
TempHeadDir = os.path.join(os.path.sep, "tmp")
TempPrefix = "test_"
TempSuffix = "_temp"
Format = b'\n%(dx)b %(who)b:\n%(data)b\n' # default format string as bytes
Expand Down
Loading