+
+ true
+ true
+ false
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 3deac4e..8f4aa8c 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -5,7 +5,7 @@ plugins {
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
}
-val libVersion by extra("2.2.1.2")
+val libVersion by extra("2.3.0")
group = "com.pi4j"
version = libVersion
diff --git a/example/build.gradle.kts b/example/build.gradle.kts
index 26b193e..eccab2a 100644
--- a/example/build.gradle.kts
+++ b/example/build.gradle.kts
@@ -16,11 +16,13 @@ repositories {
dependencies {
// implementation(project(":lib"))
- implementation("com.pi4j:pi4j-ktx:2.2.1.2")
+ implementation("com.pi4j:pi4j-ktx:2.3.0")
implementation("com.pi4j:pi4j-core:2.2.1")
implementation("com.pi4j:pi4j-plugin-raspberrypi:2.2.1")
implementation("com.pi4j:pi4j-plugin-pigpio:2.2.1")
+ implementation("com.pi4j:pi4j-plugin-linuxfs:2.2.1")
implementation("com.pi4j:pi4j-plugin-mock:2.2.1")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3")
implementation("org.slf4j:slf4j-api:1.7.32")
implementation("org.slf4j:slf4j-simple:1.7.32")
testImplementation(kotlin("test"))
diff --git a/example/src/main/kotlin/CoroutinesExample.kt b/example/src/main/kotlin/CoroutinesExample.kt
new file mode 100644
index 0000000..860028c
--- /dev/null
+++ b/example/src/main/kotlin/CoroutinesExample.kt
@@ -0,0 +1,66 @@
+import com.pi4j.io.gpio.digital.DigitalState
+import com.pi4j.io.gpio.digital.PullResistance
+import com.pi4j.ktx.console
+import com.pi4j.ktx.io.digital.digitalInput
+import com.pi4j.ktx.io.digital.digitalOutput
+import com.pi4j.ktx.io.digital.onLow
+import com.pi4j.ktx.io.digital.piGpioProvider
+import com.pi4j.ktx.pi4jAsync
+import kotlinx.coroutines.delay
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+private const val PIN_BUTTON = 24 // PIN 18 = BCM 24
+private const val PIN_LED = 22 // PIN 15 = BCM 22
+private var pressCount = 0
+
+/**
+ * This application blinks a led and counts the number the button is pressed. The blink speed increases with each
+ * button press, and after 5 presses the application finishes.
+ *
+ * This example fully describes the basic usage of Pi4J-Kotlin + Coroutines
+ *
+ * @author Muhammad Hashim (mhashim6) (https://mhashim6.me)
+ */
+fun main() {
+ pi4jAsync {
+ console {
+ digitalInput(PIN_BUTTON) {
+ id("button")
+ name("Press button")
+ pull(PullResistance.PULL_DOWN)
+ debounce(3000L)
+ piGpioProvider()
+ }.onLow {
+ pressCount++
+ +"Button was pressed for the ${pressCount}th time"
+ }
+
+ digitalOutput(PIN_LED) {
+ id("led")
+ name("LED Flasher")
+ shutdown(DigitalState.LOW)
+ initial(DigitalState.LOW)
+ piGpioProvider()
+ }.run {
+ while (pressCount < 5) {
+ +"LED ${state()}"
+ toggle()
+ delay(500L / (pressCount + 1))
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/example/src/main/kotlin/I2CExample.kt b/example/src/main/kotlin/I2CExample.kt
new file mode 100644
index 0000000..e63f911
--- /dev/null
+++ b/example/src/main/kotlin/I2CExample.kt
@@ -0,0 +1,85 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.pi4j.ktx.console
+import com.pi4j.ktx.io.i2c
+import com.pi4j.ktx.io.linuxFsI2CProvider
+import com.pi4j.ktx.io.setPin
+import com.pi4j.ktx.pi4j
+import com.pi4j.ktx.utils.binStr
+import java.lang.Thread.sleep
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+private const val TCA9534_REG_ADDR_OUT_PORT: Int = 0x01
+private const val TCA9534_REG_ADDR_CFG: Int = 0x03
+
+/**
+ * @author Muhammad Hashim (mhashim6) (https://mhashim6.me) on 28/12/2022
+ */
+fun main() {
+ pi4j {
+ i2c(1, 0x3f) {
+ id("TCA9534")
+ linuxFsI2CProvider()
+ }.use { tca9534Dev ->
+ val config = tca9534Dev.readRegister(TCA9534_REG_ADDR_CFG)
+ check(config >= 0) {
+ "Failed to read configuration from address 0x${"%02x".format(TCA9534_REG_ADDR_CFG)}"
+ }
+
+ var currentState = tca9534Dev.readRegister(TCA9534_REG_ADDR_OUT_PORT)
+ if (config != 0x00) {
+ println(
+ "TCA9534 is not configured as OUTPUT, setting register 0x${"%02x".format(TCA9534_REG_ADDR_CFG)} to 0x00"
+ )
+ currentState = 0x00
+ tca9534Dev.writeRegister(TCA9534_REG_ADDR_OUT_PORT, currentState)
+ tca9534Dev.writeRegister(TCA9534_REG_ADDR_CFG, 0x00)
+ }
+
+ tca9534Dev.run {
+ // bit 8, is pin 1 on the board itself, so set pins in reverse:
+ console {
+ currentState = setPin(currentState, 8, TCA9534_REG_ADDR_OUT_PORT)
+ +"Setting TCA9534 to new state ${currentState.binStr()}"
+ sleep(500L)
+ currentState = setPin(currentState, 8, TCA9534_REG_ADDR_OUT_PORT, false)
+ +"Setting TCA9534 to new state ${currentState.binStr()}"
+ sleep(500L)
+ currentState = setPin(currentState, 7, TCA9534_REG_ADDR_OUT_PORT)
+ +"Setting TCA9534 to new state ${currentState.binStr()}"
+ sleep(500L)
+ currentState = setPin(currentState, 7, TCA9534_REG_ADDR_OUT_PORT, false)
+ +"Setting TCA9534 to new state ${currentState.binStr()}"
+ sleep(500L)
+ }
+ }
+ }
+ }
+
+}
diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts
index 61db0a0..7f834ee 100644
--- a/lib/build.gradle.kts
+++ b/lib/build.gradle.kts
@@ -22,6 +22,7 @@ dependencies {
compileOnly("org.slf4j:slf4j-api:1.7.32")
testImplementation("org.slf4j:slf4j-api:1.7.32")
compileOnly("org.slf4j:slf4j-simple:1.7.32")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
testImplementation("org.slf4j:slf4j-simple:1.7.32")
testImplementation(kotlin("test"))
}
diff --git a/lib/src/main/kotlin/com/pi4j/ktx/Context.kt b/lib/src/main/kotlin/com/pi4j/ktx/Context.kt
index 80e6f1e..ff35a28 100644
--- a/lib/src/main/kotlin/com/pi4j/ktx/Context.kt
+++ b/lib/src/main/kotlin/com/pi4j/ktx/Context.kt
@@ -20,6 +20,10 @@ import com.pi4j.context.ContextBuilder
import com.pi4j.io.IO
import com.pi4j.platform.Platform
import com.pi4j.provider.Provider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.runBlocking
import java.io.File
import java.io.InputStream
import java.io.Reader
@@ -33,10 +37,11 @@ import java.util.*
annotation class ContextBuilderMarker
@ContextBuilderMarker
- class KontextBuilder: ContextBuilder by ContextBuilder.newInstance() {
+class KontextBuilder : ContextBuilder by ContextBuilder.newInstance() {
operator fun Platform.unaryPlus() {
add(this)
}
+
operator fun Provider, out IO<*, *, *>, out Config<*>>?.unaryPlus() {
add(this)
}
@@ -44,15 +49,19 @@ annotation class ContextBuilderMarker
operator fun Properties.unaryPlus() {
add(this)
}
+
operator fun Pair.unaryPlus() {
addProperty(this.first, this.second)
}
+
operator fun File.unaryPlus() {
addProperties(this)
}
+
operator fun Reader.unaryPlus() {
addProperties(this)
}
+
operator fun InputStream.unaryPlus() {
addProperties(this)
}
@@ -70,6 +79,24 @@ inline fun pi4j(block: Context.() -> Unit): Context {
return context
}
+/**
+ * Coroutine variant of [pi4j]
+ *
+ * Creates a new [Context] using [Pi4J.newAutoContext] and uses it in execution.
+ * Automatically calls [Context.shutdown] after [block] execution
+ * @param scope within which the coroutines will run
+ * @param block runs on a [Context] Receiver
+ */
+fun pi4jAsync(scope: CoroutineScope = CoroutineScope(Dispatchers.IO), block: suspend Context.() -> Unit): Context {
+ return runBlocking {
+ pi4j {
+ scope.async {
+ block()
+ }.await()
+ }
+ }
+}
+
inline fun buildContext(builder: KontextBuilder.() -> Unit): Context {
return KontextBuilder().run {
builder.invoke(this)
diff --git a/lib/src/main/kotlin/com/pi4j/ktx/io/I2C.kt b/lib/src/main/kotlin/com/pi4j/ktx/io/I2C.kt
new file mode 100644
index 0000000..f828492
--- /dev/null
+++ b/lib/src/main/kotlin/com/pi4j/ktx/io/I2C.kt
@@ -0,0 +1,66 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pi4j.ktx.io
+
+import com.pi4j.context.Context
+import com.pi4j.io.i2c.I2C
+import com.pi4j.io.i2c.I2CConfigBuilder
+import com.pi4j.ktx.utils.Provider
+
+/**
+ * @author Muhammad Hashim (mhashim6) (https://mhashim6.me) on 28/12/2022
+ */
+
+inline fun Context.i2c(bus: Int, device: Int, block: I2CConfigBuilder.() -> Unit): I2C =
+ create(I2C.newConfigBuilder(this).run {
+ bus(bus)
+ device(device)
+ block()
+ build()
+ })
+
+fun I2C.setPin(currentState: Int, pin: Int, regOutPort: Int, high: Boolean = true): Int {
+ val newState: Int =
+ if (high) {
+ currentState or (1 shl pin)
+ } else {
+ currentState and (1 shl pin).inv()
+ }
+
+ writeRegister(regOutPort, newState)
+ return newState
+}
+
+fun I2CConfigBuilder.mockI2cProvider() {
+ provider(Provider.MOCK_I2C.id)
+}
+
+fun I2CConfigBuilder.linuxFsI2CProvider() {
+ provider(Provider.LINUX_FS_I2C.id)
+}
\ No newline at end of file
diff --git a/lib/src/main/kotlin/com/pi4j/ktx/utils/Constants.kt b/lib/src/main/kotlin/com/pi4j/ktx/utils/Constants.kt
index 5a20864..471d8a3 100644
--- a/lib/src/main/kotlin/com/pi4j/ktx/utils/Constants.kt
+++ b/lib/src/main/kotlin/com/pi4j/ktx/utils/Constants.kt
@@ -28,4 +28,6 @@ enum class Provider(val id: String) {
PI_GPIO_ANALOG_OUTPUT("pigpio-analog-output"),
MOCK_PWM("mock-pwm"),
PI_GPIO_PWM("pigpio-pwm"),
+ MOCK_I2C("mock-i2c"),
+ LINUX_FS_I2C("linuxfs-i2c"),
}
\ No newline at end of file
diff --git a/lib/src/main/kotlin/com/pi4j/ktx/utils/Extensions.kt b/lib/src/main/kotlin/com/pi4j/ktx/utils/Extensions.kt
new file mode 100644
index 0000000..67c3128
--- /dev/null
+++ b/lib/src/main/kotlin/com/pi4j/ktx/utils/Extensions.kt
@@ -0,0 +1,46 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pi4j.ktx.utils
+
+/**
+ * @author Muhammad Hashim (mhashim6) (https://mhashim6.me) on 28/12/2022
+ */
+
+fun Byte.binStr(): String = buildString {
+ append(toInt() ushr 7 and 1)
+ append(toInt() ushr 6 and 1)
+ append(toInt() ushr 5 and 1)
+ append(toInt() ushr 4 and 1)
+ append(toInt() ushr 3 and 1)
+ append(toInt() ushr 2 and 1)
+ append(toInt() ushr 1 and 1)
+ append(toInt() ushr 0 and 1)
+}
+
+fun Int.binStr(): String = toByte().binStr()
\ No newline at end of file
diff --git a/lib/src/test/kotlin/com/pi4j/ktx/ContextTest.kt b/lib/src/test/kotlin/com/pi4j/ktx/ContextTest.kt
index 69d4ce8..10dafca 100644
--- a/lib/src/test/kotlin/com/pi4j/ktx/ContextTest.kt
+++ b/lib/src/test/kotlin/com/pi4j/ktx/ContextTest.kt
@@ -17,7 +17,13 @@ import com.pi4j.Pi4J
import com.pi4j.context.Context
import com.pi4j.plugin.mock.platform.MockPlatform
import com.pi4j.plugin.mock.provider.pwm.MockPwmProvider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.delay
import org.junit.jupiter.api.Test
+import java.lang.System.currentTimeMillis
+import java.lang.Thread.currentThread
+import java.util.concurrent.Executors
import kotlin.test.*
/**
@@ -41,6 +47,44 @@ internal class ContextTest {
}
}
+ @Test
+ fun `test pi4jAsync custom coroutine scope`() {
+ val executor = Executors.newSingleThreadExecutor()
+ var thread = currentThread().id
+ println(thread)
+ executor.execute {
+ thread = currentThread().id
+ }
+
+ pi4jAsync(CoroutineScope(executor.asCoroutineDispatcher())) {
+ println("I'm in pi4j auto context")
+
+ assertEquals(thread, currentThread().id)
+
+ val now = currentTimeMillis()
+ delay(1000L)
+ val delay = currentTimeMillis() - now
+ println("$delay")
+ assertTrue { delay in 1000L..1100L }
+ }.also {
+ assertTrue { it.isShutdown }
+ }
+ }
+
+ @Test
+ fun `test pi4jAsync coroutine scope`() {
+ val thread = currentThread().id
+ println(thread)
+
+ pi4jAsync {
+ println("I'm in pi4j auto context")
+ delay(1000L)
+ assertNotEquals(thread, currentThread().id)
+ }.also {
+ assertTrue { it.isShutdown }
+ }
+ }
+
@Test
fun `test generics`() {
context.run {
diff --git a/lib/src/test/kotlin/com/pi4j/ktx/io/I2CTest.kt b/lib/src/test/kotlin/com/pi4j/ktx/io/I2CTest.kt
new file mode 100644
index 0000000..98e508a
--- /dev/null
+++ b/lib/src/test/kotlin/com/pi4j/ktx/io/I2CTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pi4j.ktx.io
+
+import com.pi4j.Pi4J
+import com.pi4j.context.Context
+import com.pi4j.io.exception.IOAlreadyExistsException
+import com.pi4j.io.i2c.I2C
+import com.pi4j.io.i2c.I2CProvider
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+
+/**
+ * @author Muhammad Hashim (mhashim6) (https://mhashim6.me) on 28/12/2022
+ */
+internal class I2CTest {
+ private lateinit var context: Context
+
+ @BeforeTest
+ fun setup() {
+ context = Pi4J.newAutoContext()
+ }
+
+ @Test
+ fun `test i2c creation`() {
+ context.run {
+ val i2CProvider: I2CProvider = context.provider("mock-i2c")
+ val javaI2C =
+ i2CProvider.create(I2C.newConfigBuilder(context).id("TCA9534").bus(1).device(0x3f).build())
+
+ val kotlinI2C = i2c(2, 0x3f) {
+ mockI2cProvider()
+ }
+
+ assertEquals(javaI2C::class.java, kotlinI2C::class.java)
+ assertEquals(2, kotlinI2C.bus)
+
+ assertThrows {
+ i2CProvider.create(I2C.newConfigBuilder(context).id("TCA9534").bus(1).device(0x4f).build())
+ i2c(1, 0x4f) {
+ id("TCA9534")
+ mockI2cProvider()
+ }
+ }
+ }
+ }
+
+
+ @AfterTest
+ fun tearDown() {
+ context.shutdown()
+ }
+}
\ No newline at end of file