diff --git a/bom/openhab-core/pom.xml b/bom/openhab-core/pom.xml
index 1bea7c150d..df656f34fe 100644
--- a/bom/openhab-core/pom.xml
+++ b/bom/openhab-core/pom.xml
@@ -262,6 +262,12 @@
${project.version}
compile
+
+ org.openhab.core.bundles
+ org.openhab.core.io.transport.serial.purejavacomm
+ ${project.version}
+ compile
+
org.openhab.core.bundles
org.openhab.core.io.transport.serial.rxtx
diff --git a/bundles/org.openhab.core.io.transport.serial.purejavacomm/.classpath b/bundles/org.openhab.core.io.transport.serial.purejavacomm/.classpath
new file mode 100644
index 0000000000..af4894f181
--- /dev/null
+++ b/bundles/org.openhab.core.io.transport.serial.purejavacomm/.classpath
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.core.io.transport.serial.purejavacomm/.project b/bundles/org.openhab.core.io.transport.serial.purejavacomm/.project
new file mode 100644
index 0000000000..0efc39652e
--- /dev/null
+++ b/bundles/org.openhab.core.io.transport.serial.purejavacomm/.project
@@ -0,0 +1,34 @@
+
+
+ org.openhab.core.io.transport.serial.javacomm
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
+
+ 1684860371393
+
+ 30
+
+ org.eclipse.core.resources.regexFilterMatcher
+ node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
+
+
+
+
diff --git a/bundles/org.openhab.core.io.transport.serial.purejavacomm/NOTICE b/bundles/org.openhab.core.io.transport.serial.purejavacomm/NOTICE
new file mode 100644
index 0000000000..6c17d0d8a4
--- /dev/null
+++ b/bundles/org.openhab.core.io.transport.serial.purejavacomm/NOTICE
@@ -0,0 +1,14 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-core
+
diff --git a/bundles/org.openhab.core.io.transport.serial.purejavacomm/pom.xml b/bundles/org.openhab.core.io.transport.serial.purejavacomm/pom.xml
new file mode 100644
index 0000000000..125325fe89
--- /dev/null
+++ b/bundles/org.openhab.core.io.transport.serial.purejavacomm/pom.xml
@@ -0,0 +1,28 @@
+
+
+ 4.0.0
+
+
+ org.openhab.core.bundles
+ org.openhab.core.reactor.bundles
+ 4.0.0-SNAPSHOT
+
+
+ org.openhab.core.io.transport.serial.purejavacomm
+
+ openHAB Core :: Bundles :: Serial Transport for Pure Java Communications API
+
+
+
+ org.openhab.core.bundles
+ org.openhab.core.io.transport.serial
+ ${project.version}
+
+
+ org.opensmarthouse
+ purejavacomm
+ 1.0.5
+
+
+
diff --git a/bundles/org.openhab.core.io.transport.serial.purejavacomm/src/main/java/org/openhab/core/io/transport/serial/internal/PureJavaCommPortProvider.java b/bundles/org.openhab.core.io.transport.serial.purejavacomm/src/main/java/org/openhab/core/io/transport/serial/internal/PureJavaCommPortProvider.java
new file mode 100644
index 0000000000..c4514c199e
--- /dev/null
+++ b/bundles/org.openhab.core.io.transport.serial.purejavacomm/src/main/java/org/openhab/core/io/transport/serial/internal/PureJavaCommPortProvider.java
@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.io.transport.serial.internal;
+
+import java.net.URI;
+import java.util.Enumeration;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.io.transport.serial.ProtocolType;
+import org.openhab.core.io.transport.serial.ProtocolType.PathType;
+import org.openhab.core.io.transport.serial.SerialPortIdentifier;
+import org.openhab.core.io.transport.serial.SerialPortProvider;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import purejavacomm.CommPortIdentifier;
+import purejavacomm.NoSuchPortException;
+
+/**
+ *
+ * @author Łukasz Dywicki - Initial contribution
+ * @author Karel Goderis - added further methods
+ */
+@NonNullByDefault
+@Component(service = SerialPortProvider.class)
+public class PureJavaCommPortProvider implements SerialPortProvider {
+
+ private final Logger logger = LoggerFactory.getLogger(PureJavaCommPortProvider.class);
+
+ @Override
+ public @Nullable SerialPortIdentifier getPortIdentifier(URI port) {
+ CommPortIdentifier ident = null;
+ try {
+ ident = CommPortIdentifier.getPortIdentifier(port.getPath());
+ } catch (NoSuchPortException e) {
+ logger.debug("No SerialPortIdentifier found for: {}", port.getPath());
+ return null;
+ }
+ return new SerialPortIdentifierImpl(ident);
+ }
+
+ @Override
+ public Stream getAcceptedProtocols() {
+ return Stream.of(new ProtocolType(PathType.LOCAL, "purejavacomm"));
+ }
+
+ @Override
+ public Stream getSerialPortIdentifiers() {
+ @SuppressWarnings("unchecked")
+ final Enumeration ids = CommPortIdentifier.getPortIdentifiers();
+ return StreamSupport.stream(new SplitIteratorForEnumeration<>(ids), false)
+ .filter(id -> id.getPortType() == CommPortIdentifier.PORT_SERIAL)
+ .map(sid -> new SerialPortIdentifierImpl(sid));
+ }
+
+ private static class SplitIteratorForEnumeration extends Spliterators.AbstractSpliterator {
+ private final Enumeration e;
+
+ public SplitIteratorForEnumeration(final Enumeration e) {
+ super(Long.MAX_VALUE, Spliterator.ORDERED);
+ this.e = e;
+ }
+
+ @Override
+ @NonNullByDefault({})
+ public boolean tryAdvance(Consumer super T> action) {
+ if (e.hasMoreElements()) {
+ action.accept(e.nextElement());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ @NonNullByDefault({})
+ public void forEachRemaining(Consumer super T> action) {
+ while (e.hasMoreElements()) {
+ action.accept(e.nextElement());
+ }
+ }
+ }
+}
diff --git a/bundles/org.openhab.core.io.transport.serial.purejavacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortEventImpl.java b/bundles/org.openhab.core.io.transport.serial.purejavacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortEventImpl.java
new file mode 100644
index 0000000000..e2c6ad6dd2
--- /dev/null
+++ b/bundles/org.openhab.core.io.transport.serial.purejavacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortEventImpl.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.io.transport.serial.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.io.transport.serial.SerialPortEvent;
+
+/**
+ * Specific serial port event implementation.
+ *
+ * @author Łukasz Dywicki - Initial contribution
+ * @author Karel Goderis - added further methods
+ */
+@NonNullByDefault
+public class SerialPortEventImpl implements SerialPortEvent {
+
+ private final purejavacomm.SerialPortEvent event;
+
+ /**
+ * Constructor.
+ *
+ * @param event the underlying event implementation
+ */
+ public SerialPortEventImpl(final purejavacomm.SerialPortEvent event) {
+ this.event = event;
+ }
+
+ @Override
+ public int getEventType() {
+ return event.getEventType();
+ }
+
+ @Override
+ public boolean getNewValue() {
+ return event.getNewValue();
+ }
+}
diff --git a/bundles/org.openhab.core.io.transport.serial.purejavacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortIdentifierImpl.java b/bundles/org.openhab.core.io.transport.serial.purejavacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortIdentifierImpl.java
new file mode 100644
index 0000000000..45f024ab42
--- /dev/null
+++ b/bundles/org.openhab.core.io.transport.serial.purejavacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortIdentifierImpl.java
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.io.transport.serial.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.io.transport.serial.PortInUseException;
+import org.openhab.core.io.transport.serial.SerialPort;
+import org.openhab.core.io.transport.serial.SerialPortIdentifier;
+
+import purejavacomm.CommPortIdentifier;
+
+/**
+ * Specific serial port identifier implementation.
+ *
+ * @author Łukasz Dywicki - Initial contribution
+ * @author Karel Goderis - added further methods
+ */
+@NonNullByDefault
+public class SerialPortIdentifierImpl implements SerialPortIdentifier {
+
+ final CommPortIdentifier id;
+
+ /**
+ * Constructor.
+ *
+ * @param id the underlying comm port identifier implementation
+ */
+ public SerialPortIdentifierImpl(final CommPortIdentifier id) {
+ this.id = id;
+ }
+
+ @Override
+ public String getName() {
+ final String name = id.getName();
+ return name != null ? name : "";
+ }
+
+ @Override
+ public SerialPort open(String owner, int timeout) throws PortInUseException {
+
+ try {
+ return new SerialPortImpl((purejavacomm.SerialPort) id.open(owner, timeout));
+ } catch (purejavacomm.PortInUseException e) {
+ String message = e.getMessage();
+ if (message != null) {
+ throw new PortInUseException(message, e);
+ } else {
+ throw new PortInUseException(e);
+ }
+ }
+ }
+
+ @Override
+ public boolean isCurrentlyOwned() {
+ return id.isCurrentlyOwned();
+ }
+
+ @Override
+ public @Nullable String getCurrentOwner() {
+ return id.getCurrentOwner();
+ }
+
+ public String toString() {
+ return getName() + " (pure java comm)";
+ }
+}
diff --git a/bundles/org.openhab.core.io.transport.serial.purejavacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortImpl.java b/bundles/org.openhab.core.io.transport.serial.purejavacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortImpl.java
new file mode 100644
index 0000000000..bbdff194ee
--- /dev/null
+++ b/bundles/org.openhab.core.io.transport.serial.purejavacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortImpl.java
@@ -0,0 +1,260 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.io.transport.serial.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.TooManyListenersException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.io.transport.serial.SerialPort;
+import org.openhab.core.io.transport.serial.SerialPortEventListener;
+import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
+
+import purejavacomm.SerialPortEvent;
+
+/**
+ * Specific serial port implementation.
+ *
+ * @author Łukasz Dywicki - Initial contribution
+ * @author Karel Goderis - added further methods
+ */
+@NonNullByDefault
+public class SerialPortImpl implements SerialPort {
+
+ private final purejavacomm.SerialPort sp;
+
+ /**
+ * Constructor.
+ *
+ * @param sp the underlying serial port implementation
+ */
+ public SerialPortImpl(final purejavacomm.SerialPort sp) {
+ this.sp = sp;
+ }
+
+ @Override
+ public void close() {
+ sp.close();
+ }
+
+ @Override
+ public void setSerialPortParams(int baudrate, int dataBits, int stopBits, int parity)
+ throws UnsupportedCommOperationException {
+ try {
+ sp.setSerialPortParams(baudrate, dataBits, stopBits, parity);
+ } catch (purejavacomm.UnsupportedCommOperationException ex) {
+ throw new UnsupportedCommOperationException(ex);
+ }
+ }
+
+ @Override
+ public @Nullable InputStream getInputStream() throws IOException {
+ return sp.getInputStream();
+ }
+
+ @Override
+ public @Nullable OutputStream getOutputStream() throws IOException {
+ return sp.getOutputStream();
+ }
+
+ @Override
+ public void addEventListener(SerialPortEventListener listener) throws TooManyListenersException {
+ sp.addEventListener(new purejavacomm.SerialPortEventListener() {
+ @Override
+ public void serialEvent(final @Nullable SerialPortEvent event) {
+ if (event == null) {
+ return;
+ }
+ listener.serialEvent(new org.openhab.core.io.transport.serial.SerialPortEvent() {
+ @Override
+ public int getEventType() {
+ return event.getEventType();
+ }
+
+ @Override
+ public boolean getNewValue() {
+ return event.getNewValue();
+ }
+ });
+ }
+ });
+ }
+
+ @Override
+ public void removeEventListener() {
+ sp.removeEventListener();
+ }
+
+ @Override
+ public void notifyOnDataAvailable(boolean enable) {
+ sp.notifyOnDataAvailable(enable);
+ }
+
+ @Override
+ public void notifyOnBreakInterrupt(boolean enable) {
+ sp.notifyOnBreakInterrupt(enable);
+ }
+
+ @Override
+ public void notifyOnFramingError(boolean enable) {
+ sp.notifyOnFramingError(enable);
+ }
+
+ @Override
+ public void notifyOnOverrunError(boolean enable) {
+ sp.notifyOnOverrunError(enable);
+ }
+
+ @Override
+ public void notifyOnParityError(boolean enable) {
+ sp.notifyOnParityError(enable);
+ }
+
+ @Override
+ public void enableReceiveTimeout(int timeout) throws UnsupportedCommOperationException {
+ if (timeout < 0) {
+ throw new IllegalArgumentException(String.format("timeout must be non negative (is: %d)", timeout));
+ }
+ try {
+ sp.enableReceiveTimeout(timeout);
+ } catch (purejavacomm.UnsupportedCommOperationException ex) {
+ throw new UnsupportedCommOperationException(ex);
+ }
+ }
+
+ @Override
+ public void disableReceiveTimeout() {
+ sp.disableReceiveTimeout();
+ }
+
+ @Override
+ public String getName() {
+ return sp.getName();
+ }
+
+ @Override
+ public void setFlowControlMode(int flowcontrolRtsctsOut) throws UnsupportedCommOperationException {
+ try {
+ sp.setFlowControlMode(flowcontrolRtsctsOut);
+ } catch (purejavacomm.UnsupportedCommOperationException e) {
+ throw new UnsupportedCommOperationException(e);
+ }
+ }
+
+ @Override
+ public void enableReceiveThreshold(int i) throws UnsupportedCommOperationException {
+ try {
+ sp.enableReceiveThreshold(i);
+ } catch (purejavacomm.UnsupportedCommOperationException e) {
+ throw new UnsupportedCommOperationException(e);
+ }
+ }
+
+ @Override
+ public int getBaudRate() {
+ return sp.getBaudRate();
+ }
+
+ @Override
+ public int getDataBits() {
+ return sp.getDataBits();
+ }
+
+ @Override
+ public int getStopBits() {
+ return sp.getStopBits();
+ }
+
+ @Override
+ public int getParity() {
+ return sp.getParity();
+ }
+
+ @Override
+ public void notifyOnOutputEmpty(boolean enable) {
+ sp.notifyOnOutputEmpty(enable);
+ }
+
+ @Override
+ public void notifyOnCTS(boolean enable) {
+ sp.notifyOnCTS(enable);
+ }
+
+ @Override
+ public void notifyOnDSR(boolean enable) {
+ sp.notifyOnDSR(enable);
+ }
+
+ @Override
+ public void notifyOnRingIndicator(boolean enable) {
+ sp.notifyOnRingIndicator(enable);
+ }
+
+ @Override
+ public void notifyOnCarrierDetect(boolean enable) {
+ sp.notifyOnCarrierDetect(enable);
+ }
+
+ @Override
+ public int getFlowControlMode() {
+ return getFlowControlMode();
+ }
+
+ @Override
+ public void setRTS(boolean enable) {
+ sp.setRTS(enable);
+ }
+
+ @Override
+ public boolean isRTS() {
+ return sp.isRTS();
+ }
+
+ @Override
+ public void setDTR(boolean state) {
+ sp.setDTR(state);
+ }
+
+ @Override
+ public boolean isDTR() {
+ return sp.isDTR();
+ }
+
+ @Override
+ public boolean isCTS() {
+ return sp.isCTS();
+ }
+
+ @Override
+ public boolean isDSR() {
+ return sp.isDSR();
+ }
+
+ @Override
+ public boolean isCD() {
+ return sp.isCD();
+ }
+
+ @Override
+ public boolean isRI() {
+ return sp.isRI();
+ }
+
+ @Override
+ public void sendBreak(int duration) {
+ sp.sendBreak(duration);
+ }
+}
diff --git a/bundles/pom.xml b/bundles/pom.xml
index c0fac698fa..8f269564e5 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -73,6 +73,7 @@
org.openhab.core.io.transport.mqtt
org.openhab.core.io.transport.serial
org.openhab.core.io.transport.serial.javacomm
+ org.openhab.core.io.transport.serial.purejavacomm
org.openhab.core.io.transport.serial.rxtx
org.openhab.core.io.transport.serial.rxtx.rfc2217
org.openhab.core.io.transport.upnp
diff --git a/features/karaf/openhab-core/src/main/feature/feature.xml b/features/karaf/openhab-core/src/main/feature/feature.xml
index 5fe065535e..84fd38b89f 100644
--- a/features/karaf/openhab-core/src/main/feature/feature.xml
+++ b/features/karaf/openhab-core/src/main/feature/feature.xml
@@ -237,6 +237,19 @@
mvn:org.openhab.core.bundles/org.openhab.core.io.transport.serial.javacomm/${project.version}
+
+ req:osgi.native;filter:="(&(osgi.native.osname=MacOSX)(osgi.native.processor=AArch64))"
+
+ openhab-core-base
+
+ openhab.tp;filter:="(&(feature=serial)(impl=purejavacomm))"
+ openhab.tp-serial-purejavacomm
+
+ mvn:org.openhab.core.bundles/org.openhab.core.io.transport.serial/${project.version}
+ mvn:org.openhab.core.bundles/org.openhab.core.io.transport.serial.purejavacomm/${project.version}
+
+
+
openhab-core-base
diff --git a/features/karaf/openhab-tp/src/main/feature/feature.xml b/features/karaf/openhab-tp/src/main/feature/feature.xml
index 96bfdbc5a4..7e6efc90c0 100644
--- a/features/karaf/openhab-tp/src/main/feature/feature.xml
+++ b/features/karaf/openhab-tp/src/main/feature/feature.xml
@@ -232,6 +232,15 @@
mvn:org.eclipse.kura/org.eclipse.soda.dk.comm.x86_64/1.2.201
+
+ req:osgi.native;filter:="(&(osgi.native.osname=MacOSX)(osgi.native.processor=AArch64))"
+
+ openhab.tp;feature=serial;impl=purejavacomm
+ openhab.tp-jna
+ mvn:org.opensmarthouse/purejavacomm/1.0.5
+
+
+
openhab.tp;feature=serial;impl=rxtx
mvn:com.neuronrobotics/nrjavaserial/5.2.1.OH1