HB, OTA etc
This commit is contained in:
BIN
backend/backend.zip
Normal file
BIN
backend/backend.zip
Normal file
Binary file not shown.
@@ -20,6 +20,7 @@ dependencies {
|
|||||||
implementation("ch.qos.logback:logback-classic:1.4.14")
|
implementation("ch.qos.logback:logback-classic:1.4.14")
|
||||||
implementation("org.mariadb.jdbc:mariadb-java-client:3.3.3")
|
implementation("org.mariadb.jdbc:mariadb-java-client:3.3.3")
|
||||||
implementation("com.zaxxer:HikariCP:5.1.0")
|
implementation("com.zaxxer:HikariCP:5.1.0")
|
||||||
|
implementation("io.ktor:ktor-server-compression:2.3.7")
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,3 +45,48 @@ tasks.processResources {
|
|||||||
into("db/migration")
|
into("db/migration")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.register("deploy") {
|
||||||
|
dependsOn("jar")
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
val jarFile = file("build/libs/backend.jar")
|
||||||
|
|
||||||
|
if (!jarFile.exists()) {
|
||||||
|
throw GradleException("Jar not found: ${jarFile.absolutePath}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun runCommand(vararg cmd: String) {
|
||||||
|
println("Running: ${cmd.joinToString(" ")}")
|
||||||
|
|
||||||
|
val process = ProcessBuilder(*cmd)
|
||||||
|
.inheritIO()
|
||||||
|
.start()
|
||||||
|
|
||||||
|
val exitCode = process.waitFor()
|
||||||
|
if (exitCode != 0) {
|
||||||
|
throw GradleException("Command failed: ${cmd.joinToString(" ")}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// upload
|
||||||
|
runCommand(
|
||||||
|
"scp",
|
||||||
|
jarFile.absolutePath,
|
||||||
|
"home-iot:/tmp/backend.jar"
|
||||||
|
)
|
||||||
|
|
||||||
|
// deploy
|
||||||
|
runCommand(
|
||||||
|
"ssh",
|
||||||
|
"home-iot",
|
||||||
|
"sudo mv /tmp/backend.jar /opt/iot-backend/app.jar && sudo systemctl restart iot-backend"
|
||||||
|
)
|
||||||
|
|
||||||
|
runCommand("ssh",
|
||||||
|
"home-iot",
|
||||||
|
"systemctl status iot-backend --no-pager")
|
||||||
|
|
||||||
|
println("Deploy completed")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,42 +15,32 @@ import org.pavloveugene.iot.backend.db.Migration
|
|||||||
import org.pavloveugene.iot.backend.db.runMigrations
|
import org.pavloveugene.iot.backend.db.runMigrations
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import org.pavloveugene.iot.backend.routes.*
|
import org.pavloveugene.iot.backend.routes.*
|
||||||
|
import org.pavloveugene.iot.backend.services.executeCleanup
|
||||||
|
import org.pavloveugene.iot.backend.services.runNormalizeLoop
|
||||||
|
import org.pavloveugene.iot.backend.services.startKtorServer
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
fun main() {
|
fun main(args: Array<String>) {
|
||||||
Database.dataSource
|
|
||||||
|
Database.init()
|
||||||
|
|
||||||
runMigrations()
|
runMigrations()
|
||||||
|
|
||||||
val server = embeddedServer(
|
val mode = args.firstOrNull()
|
||||||
Netty,
|
|
||||||
port = AppConfig.serverPort,
|
when (mode) {
|
||||||
host = AppConfig.serverHost,
|
"--normalize-data", "normalize-data" -> {
|
||||||
) {
|
runNormalizeLoop()
|
||||||
install(ContentNegotiation) {
|
|
||||||
json()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
install(WebSockets) {
|
"--cleanup", "cleanup" -> {
|
||||||
pingPeriod = Duration.ofSeconds(15)
|
executeCleanup()
|
||||||
timeout = Duration.ofSeconds(30)
|
|
||||||
maxFrameSize = Long.MAX_VALUE
|
|
||||||
masking = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
routing {
|
else -> {
|
||||||
protocolRoutes()
|
startKtorServer()
|
||||||
protocolWebSocket()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Runtime.getRuntime().addShutdownHook(Thread {
|
exitProcess(0)
|
||||||
println("Shutting down...")
|
|
||||||
try {
|
|
||||||
server.stop(1000, 2000)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("Shutdown error: ${e.message}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
server.start(wait = true)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ package org.pavloveugene.iot.backend.db
|
|||||||
import com.zaxxer.hikari.HikariConfig
|
import com.zaxxer.hikari.HikariConfig
|
||||||
import com.zaxxer.hikari.HikariDataSource
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
import org.pavloveugene.iot.backend.config.AppConfig
|
import org.pavloveugene.iot.backend.config.AppConfig
|
||||||
|
import java.sql.Statement
|
||||||
import kotlin.getValue
|
import kotlin.getValue
|
||||||
|
|
||||||
object Database {
|
object Database {
|
||||||
|
fun init() {
|
||||||
|
val ds = dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
val dataSource: HikariDataSource by lazy {
|
val dataSource: HikariDataSource by lazy {
|
||||||
val config = HikariConfig().apply {
|
val config = HikariConfig().apply {
|
||||||
@@ -20,9 +24,93 @@ object Database {
|
|||||||
idleTimeout = 30000
|
idleTimeout = 30000
|
||||||
maxLifetime = 1800000
|
maxLifetime = 1800000
|
||||||
|
|
||||||
isAutoCommit = false
|
isAutoCommit = true
|
||||||
}
|
}
|
||||||
|
|
||||||
HikariDataSource(config)
|
HikariDataSource(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun execute(sql: String, params: List<Any?> = emptyList()): Int {
|
||||||
|
dataSource.connection.use { conn ->
|
||||||
|
conn.prepareStatement(sql).use { stmt ->
|
||||||
|
|
||||||
|
params.forEachIndexed { i, p ->
|
||||||
|
stmt.setObject(i + 1, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stmt.executeUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun queryOne(sql: String, params: List<Any?> = emptyList()): Map<String, Any?>? {
|
||||||
|
dataSource.connection.use { conn ->
|
||||||
|
conn.prepareStatement(sql).use { stmt ->
|
||||||
|
|
||||||
|
params.forEachIndexed { i, p ->
|
||||||
|
stmt.setObject(i + 1, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
val rs = stmt.executeQuery()
|
||||||
|
if (!rs.next()) return null
|
||||||
|
|
||||||
|
val meta = rs.metaData
|
||||||
|
val map = mutableMapOf<String, Any?>()
|
||||||
|
|
||||||
|
for (i in 1..meta.columnCount) {
|
||||||
|
map[meta.getColumnLabel(i)] = rs.getObject(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun query(sql: String, params: List<Any?> = emptyList()): List<Map<String, Any?>> {
|
||||||
|
dataSource.connection.use { conn ->
|
||||||
|
conn.prepareStatement(sql).use { stmt ->
|
||||||
|
|
||||||
|
params.forEachIndexed { i, p ->
|
||||||
|
stmt.setObject(i + 1, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
val rs = stmt.executeQuery()
|
||||||
|
val meta = rs.metaData
|
||||||
|
|
||||||
|
val result = mutableListOf<Map<String, Any?>>()
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
val map = mutableMapOf<String, Any?>()
|
||||||
|
|
||||||
|
for (i in 1..meta.columnCount) {
|
||||||
|
map[meta.getColumnLabel(i)] = rs.getObject(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.add(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insertAndReturnId(sql: String, params: List<Any?> = emptyList()): Long {
|
||||||
|
dataSource.connection.use { conn ->
|
||||||
|
conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS).use { stmt ->
|
||||||
|
|
||||||
|
params.forEachIndexed { i, p ->
|
||||||
|
stmt.setObject(i + 1, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt.executeUpdate()
|
||||||
|
|
||||||
|
val rs = stmt.generatedKeys
|
||||||
|
if (rs.next()) {
|
||||||
|
return rs.getLong(1)
|
||||||
|
} else {
|
||||||
|
error("No generated key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.pavloveugene.iot.backend.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EventDto (
|
||||||
|
val type: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package org.pavloveugene.iot.backend.routes
|
||||||
|
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.http.content.PartData
|
||||||
|
import io.ktor.http.content.forEachPart
|
||||||
|
import io.ktor.http.content.streamProvider
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.request.receiveMultipart
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.response.respondFile
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.post
|
||||||
|
import org.pavloveugene.iot.backend.db.Database
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun Route.firmwareRouting() {
|
||||||
|
uploadFirmware()
|
||||||
|
getFirmware()
|
||||||
|
post("/ota_trigger") {
|
||||||
|
// send WS command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.uploadFirmware() {
|
||||||
|
post("/firmware_upload") {
|
||||||
|
val multipart = call.receiveMultipart()
|
||||||
|
|
||||||
|
var deviceId: Long? = null
|
||||||
|
var version: Int
|
||||||
|
var fileBytes: ByteArray? = null
|
||||||
|
|
||||||
|
multipart.forEachPart { part ->
|
||||||
|
when (part) {
|
||||||
|
is PartData.FormItem -> {
|
||||||
|
when (part.name) {
|
||||||
|
"device_id" -> deviceId = part.value.toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is PartData.FileItem -> {
|
||||||
|
fileBytes = part.streamProvider().readBytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
part.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceId == null || fileBytes == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, "Missing fields")
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 генерим имя файла
|
||||||
|
val filename = "${java.util.UUID.randomUUID()}.bin"
|
||||||
|
val path = "storage/firmware/$filename"
|
||||||
|
|
||||||
|
// 👉 сохраняем файл
|
||||||
|
java.io.File(path).apply {
|
||||||
|
parentFile.mkdirs()
|
||||||
|
writeBytes(fileBytes!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 считаем sha256
|
||||||
|
val sha256 = java.security.MessageDigest
|
||||||
|
.getInstance("SHA-256")
|
||||||
|
.digest(fileBytes!!)
|
||||||
|
.joinToString("") { "%02x".format(it) }
|
||||||
|
|
||||||
|
// 👉 сохраняем в БД
|
||||||
|
while (true) {
|
||||||
|
version = ((Database.queryOne(
|
||||||
|
"""
|
||||||
|
select coalesce(max(version), 0) + 1 as v
|
||||||
|
from firmware
|
||||||
|
where device_id = ?
|
||||||
|
""",
|
||||||
|
listOf(deviceId)
|
||||||
|
)?.get("v") as Number?)?.toInt() ?: 1)
|
||||||
|
|
||||||
|
try {
|
||||||
|
Database.execute(
|
||||||
|
"""
|
||||||
|
insert into firmware (device_id, version, path, sha256, size)
|
||||||
|
values (?, ?, ?, ?, ?)
|
||||||
|
""", listOf(
|
||||||
|
deviceId,
|
||||||
|
version,
|
||||||
|
path,
|
||||||
|
sha256,
|
||||||
|
fileBytes.size
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
break
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Retry insert firmware: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
call.respond(
|
||||||
|
mapOf(
|
||||||
|
"status" to "ok",
|
||||||
|
"filename" to filename,
|
||||||
|
"version" to version,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.getFirmware() {
|
||||||
|
get("/firmware/download") {
|
||||||
|
val id = call.parameters["id"]!!.toInt()
|
||||||
|
|
||||||
|
val row = Database.queryOne(
|
||||||
|
"select path from firmware where id=?",
|
||||||
|
listOf(id)
|
||||||
|
)
|
||||||
|
if (row == null) {
|
||||||
|
return@get call.respond(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
val path = row["path"] as String
|
||||||
|
val file = File(path)
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
return@get call.respond(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
call.respondFile(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,29 +7,20 @@ import io.ktor.server.response.*
|
|||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.pavloveugene.iot.backend.dto.TelemetryDto
|
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
|
||||||
import org.pavloveugene.iot.backend.config.AppConfig
|
import org.pavloveugene.iot.backend.config.AppConfig
|
||||||
import org.pavloveugene.iot.backend.services.ProtocolService
|
import org.pavloveugene.iot.backend.services.ProtocolService
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
fun Route.protocolRoutes() {
|
fun Route.protocolRoutes() {
|
||||||
route(AppConfig.apiPrefix+"/protocol") {
|
|
||||||
|
route(AppConfig.apiPrefix + "/protocol") {
|
||||||
|
|
||||||
get("/health") {
|
get("/health") {
|
||||||
call.respond(HttpStatusCode.OK, mapOf("status" to "ok"))
|
call.respond("ok")
|
||||||
}
|
|
||||||
|
|
||||||
post("/message") {
|
|
||||||
val json = Json {
|
|
||||||
ignoreUnknownKeys = false
|
|
||||||
}
|
|
||||||
|
|
||||||
val protocolService = ProtocolService(json)
|
|
||||||
|
|
||||||
val msg = call.receive<MessageDto>()
|
|
||||||
|
|
||||||
protocolService.handleMessage(msg)
|
|
||||||
|
|
||||||
call.respond(HttpStatusCode.Accepted)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,20 +4,12 @@ import MessageDto
|
|||||||
import io.ktor.server.websocket.*
|
import io.ktor.server.websocket.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import io.ktor.websocket.*
|
import io.ktor.websocket.*
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.utils.io.CancellationException
|
import io.ktor.utils.io.CancellationException
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.encodeToJsonElement
|
|
||||||
import org.pavloveugene.iot.backend.config.AppConfig
|
import org.pavloveugene.iot.backend.config.AppConfig
|
||||||
import org.pavloveugene.iot.backend.dto.ProtocolMessage
|
import org.pavloveugene.iot.backend.services.DeviceConnections
|
||||||
import org.pavloveugene.iot.backend.dto.TelemetryDto
|
|
||||||
import org.pavloveugene.iot.backend.services.ProtocolService
|
import org.pavloveugene.iot.backend.services.ProtocolService
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.time.Duration
|
|
||||||
|
|
||||||
fun Route.protocolWebSocket() {
|
fun Route.protocolWebSocket() {
|
||||||
val json = Json {
|
val json = Json {
|
||||||
@@ -27,8 +19,11 @@ fun Route.protocolWebSocket() {
|
|||||||
val protocolService = ProtocolService(json)
|
val protocolService = ProtocolService(json)
|
||||||
|
|
||||||
webSocket(AppConfig.wsPath) {
|
webSocket(AppConfig.wsPath) {
|
||||||
|
|
||||||
println("WS connected")
|
println("WS connected")
|
||||||
|
|
||||||
|
var devId: UInt? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (frame in incoming) {
|
for (frame in incoming) {
|
||||||
if (frame is Frame.Text) {
|
if (frame is Frame.Text) {
|
||||||
@@ -43,7 +38,8 @@ fun Route.protocolWebSocket() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
protocolService.handleMessage(msg)
|
devId = msg.d
|
||||||
|
protocolService.handleMessage(msg, this)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("WS handler error: ${e.message}")
|
println("WS handler error: ${e.message}")
|
||||||
}
|
}
|
||||||
@@ -56,7 +52,10 @@ fun Route.protocolWebSocket() {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("WS error: ${e.message}")
|
println("WS error: ${e.message}")
|
||||||
} finally {
|
} finally {
|
||||||
println("WS disconnected")
|
devId?.let {
|
||||||
|
DeviceConnections.unregister(devId)
|
||||||
|
println("WS disconnected: $it")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
package org.pavloveugene.iot.backend.routes
|
|
||||||
|
|
||||||
import io.ktor.http.HttpStatusCode
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import org.pavloveugene.iot.backend.db.Database
|
|
||||||
|
|
||||||
fun Application.configureRouting() {
|
|
||||||
routing {
|
|
||||||
get("/api/v1/health") {
|
|
||||||
try {
|
|
||||||
Database.dataSource.connection.use { conn ->
|
|
||||||
conn.createStatement().execute("SELECT 1")
|
|
||||||
}
|
|
||||||
call.respondText("OK")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
call.respond(HttpStatusCode.InternalServerError, "DB ERROR")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get("/api/v1/ready") {
|
|
||||||
// например:
|
|
||||||
// - миграции применены
|
|
||||||
// - WS сервис готов
|
|
||||||
call.respondText("READY")
|
|
||||||
}
|
|
||||||
get("/live") {
|
|
||||||
call.respondText("ALIVE")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package org.pavloveugene.iot.backend.routes
|
|
||||||
|
|
||||||
class WsRoutes {
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.pavloveugene.iot.backend.services
|
||||||
|
|
||||||
|
import org.pavloveugene.iot.backend.db.Database
|
||||||
|
import java.sql.SQLException
|
||||||
|
|
||||||
|
fun executeCleanup() {
|
||||||
|
val ds = Database.dataSource
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
val cutoff = start / 1000 - 60 * 60 * 24 * 2
|
||||||
|
|
||||||
|
println("Begin cleanup")
|
||||||
|
|
||||||
|
ds.connection.use { conn ->
|
||||||
|
conn.autoCommit = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
var total = 0
|
||||||
|
|
||||||
|
do {
|
||||||
|
val deleted = conn.prepareStatement("""
|
||||||
|
delete t
|
||||||
|
from telemetry t
|
||||||
|
join (
|
||||||
|
select id
|
||||||
|
from telemetry
|
||||||
|
where ts < ? and processed = true
|
||||||
|
limit 1000
|
||||||
|
) p on p.id = t.id
|
||||||
|
""".trimIndent()).use { ps ->
|
||||||
|
ps.setLong(1, cutoff)
|
||||||
|
ps.executeUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
total += deleted
|
||||||
|
|
||||||
|
if (deleted > 0) {
|
||||||
|
println("Deleted $deleted rows (total $total)")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
} while (deleted > 0)
|
||||||
|
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
conn.rollback()
|
||||||
|
throw RuntimeException("Error during cleanup", e)
|
||||||
|
} finally {
|
||||||
|
conn.autoCommit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Cleanup complete in ${System.currentTimeMillis() - start} ms")
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.pavloveugene.iot.backend.services
|
||||||
|
|
||||||
|
import io.ktor.websocket.WebSocketSession
|
||||||
|
|
||||||
|
data class DeviceConnection(
|
||||||
|
val session: WebSocketSession,
|
||||||
|
var lastSeen: Long
|
||||||
|
)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package org.pavloveugene.iot.backend.services
|
||||||
|
|
||||||
|
import io.ktor.websocket.WebSocketSession
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
object DeviceConnections {
|
||||||
|
private val map = ConcurrentHashMap<UInt, DeviceConnection>()
|
||||||
|
|
||||||
|
fun register(deviceId: UInt, connection: DeviceConnection) {
|
||||||
|
map[deviceId] = connection
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unregister(deviceId: UInt) {
|
||||||
|
map.remove(deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(deviceId: UInt): DeviceConnection? = map[deviceId]
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.pavloveugene.iot.backend.services
|
||||||
|
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.engine.embeddedServer
|
||||||
|
import io.ktor.server.netty.Netty
|
||||||
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
import io.ktor.server.routing.routing
|
||||||
|
import io.ktor.server.websocket.WebSockets
|
||||||
|
import io.ktor.server.websocket.pingPeriod
|
||||||
|
import io.ktor.server.websocket.timeout
|
||||||
|
import io.netty.handler.codec.compression.StandardCompressionOptions.gzip
|
||||||
|
import org.pavloveugene.iot.backend.config.AppConfig
|
||||||
|
import org.pavloveugene.iot.backend.routes.protocolRoutes
|
||||||
|
import org.pavloveugene.iot.backend.routes.protocolWebSocket
|
||||||
|
import java.time.Duration
|
||||||
|
import io.ktor.server.plugins.compression.*
|
||||||
|
import org.pavloveugene.iot.backend.routes.firmwareRouting
|
||||||
|
|
||||||
|
fun startKtorServer() {
|
||||||
|
val server = embeddedServer(
|
||||||
|
Netty,
|
||||||
|
port = AppConfig.serverPort,
|
||||||
|
host = AppConfig.serverHost,
|
||||||
|
) {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json()
|
||||||
|
}
|
||||||
|
|
||||||
|
install(WebSockets) {
|
||||||
|
pingPeriod = Duration.ofSeconds(15)
|
||||||
|
timeout = Duration.ofSeconds(30)
|
||||||
|
maxFrameSize = Long.MAX_VALUE
|
||||||
|
masking = false
|
||||||
|
}
|
||||||
|
|
||||||
|
routing {
|
||||||
|
protocolRoutes()
|
||||||
|
protocolWebSocket()
|
||||||
|
firmwareRouting()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Runtime.getRuntime().addShutdownHook(Thread {
|
||||||
|
println("Shutting down...")
|
||||||
|
try {
|
||||||
|
server.stop(1000, 2000)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Shutdown error: ${e.message}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
server.start(wait = true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
package org.pavloveugene.iot.backend.services
|
||||||
|
|
||||||
|
import org.pavloveugene.iot.backend.db.Database
|
||||||
|
|
||||||
|
fun runNormalizeLoop() {
|
||||||
|
var total = 0
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
verifyData()
|
||||||
|
do {
|
||||||
|
val count = normalizeBatch()
|
||||||
|
total += count
|
||||||
|
println("Processed batch: $count")
|
||||||
|
} while (count > 0)
|
||||||
|
|
||||||
|
println("Done. Total processed: $total in ${System.currentTimeMillis() - start} ms")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun normalizeBatch(): Int {
|
||||||
|
val ds = Database.dataSource
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
|
||||||
|
ds.connection.use { conn ->
|
||||||
|
|
||||||
|
conn.autoCommit = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
conn.createStatement().use { stmt ->
|
||||||
|
stmt.executeUpdate(
|
||||||
|
"""
|
||||||
|
create temporary table pack as
|
||||||
|
select t.id
|
||||||
|
from telemetry t
|
||||||
|
where t.processed=false and t.defective=false
|
||||||
|
order by t.id
|
||||||
|
limit 1000
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
stmt.executeQuery("select count(0) from pack").use { rs ->
|
||||||
|
rs.next()
|
||||||
|
count = rs.getInt(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
|
||||||
|
stmt.executeUpdate(
|
||||||
|
"""
|
||||||
|
INSERT INTO telemetry_data (device_id, ts, metric, source, value, unit)
|
||||||
|
SELECT t.device_id
|
||||||
|
, t.ts * 1000 + CAST(JSON_UNQUOTE(JSON_EXTRACT(t.payload, CONCAT('$[', seq.i, '][0]'))) AS double)
|
||||||
|
, t.metric
|
||||||
|
, t.source
|
||||||
|
, CAST(JSON_UNQUOTE(JSON_EXTRACT(t.payload, CONCAT('$[', seq.i, '][1]'))) AS DOUBLE) * coalesce(u.multiplier, 1)
|
||||||
|
, coalesce(u.target_unit, t.unit)
|
||||||
|
FROM pack p
|
||||||
|
join telemetry t on t.id = p.id
|
||||||
|
JOIN ( SELECT 0 AS i
|
||||||
|
UNION ALL SELECT 1
|
||||||
|
UNION ALL SELECT 2
|
||||||
|
UNION ALL SELECT 3
|
||||||
|
UNION ALL SELECT 4
|
||||||
|
UNION ALL SELECT 5
|
||||||
|
UNION ALL SELECT 6
|
||||||
|
UNION ALL SELECT 7
|
||||||
|
UNION ALL SELECT 8
|
||||||
|
UNION ALL SELECT 9
|
||||||
|
UNION ALL SELECT 10
|
||||||
|
UNION ALL SELECT 11
|
||||||
|
UNION ALL SELECT 12
|
||||||
|
UNION ALL SELECT 13
|
||||||
|
UNION ALL SELECT 14
|
||||||
|
UNION ALL SELECT 15
|
||||||
|
UNION ALL SELECT 16
|
||||||
|
UNION ALL SELECT 17
|
||||||
|
UNION ALL SELECT 18
|
||||||
|
UNION ALL SELECT 19
|
||||||
|
UNION ALL SELECT 20
|
||||||
|
UNION ALL SELECT 21
|
||||||
|
UNION ALL SELECT 22
|
||||||
|
UNION ALL SELECT 23
|
||||||
|
UNION ALL SELECT 24
|
||||||
|
UNION ALL SELECT 25
|
||||||
|
UNION ALL SELECT 26
|
||||||
|
UNION ALL SELECT 27
|
||||||
|
UNION ALL SELECT 28
|
||||||
|
UNION ALL SELECT 29
|
||||||
|
UNION ALL SELECT 30
|
||||||
|
UNION ALL SELECT 31
|
||||||
|
) AS seq ON seq.i < JSON_LENGTH(t.payload)
|
||||||
|
left join units u on u.unit = t.unit
|
||||||
|
WHERE JSON_EXTRACT(t.payload, CONCAT('$[', seq.i, ']')) IS NOT NULL
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
stmt.executeUpdate(
|
||||||
|
"""
|
||||||
|
update
|
||||||
|
telemetry t
|
||||||
|
join pack p on t.id = p.id
|
||||||
|
set t.processed = true
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt.executeUpdate("drop temporary table pack")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
conn.rollback()
|
||||||
|
throw RuntimeException("Error during normalize", e)
|
||||||
|
} finally {
|
||||||
|
conn.autoCommit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyData(): Boolean {
|
||||||
|
val ds = Database.dataSource
|
||||||
|
var ret = true;
|
||||||
|
|
||||||
|
println("Executing verification")
|
||||||
|
|
||||||
|
ds.connection.use { conn ->
|
||||||
|
|
||||||
|
conn.autoCommit = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
conn.createStatement().use { stmt ->
|
||||||
|
stmt.executeUpdate(
|
||||||
|
"""
|
||||||
|
create temporary table err as
|
||||||
|
select t.id
|
||||||
|
, case
|
||||||
|
when t.unit = '' then 'No unit'
|
||||||
|
else 'Invalid unit'
|
||||||
|
end reason
|
||||||
|
from telemetry t
|
||||||
|
where t.processed=false and t.defective=false
|
||||||
|
and (t.unit = '' or (
|
||||||
|
t.unit not in (select unit from units)
|
||||||
|
and t.unit not in (select target_unit from units)
|
||||||
|
and t.unit not in ('v', 'a', 'm', 'g', 'kg', 'raw')
|
||||||
|
))
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
stmt.executeQuery("select count(0) from err").use { rs ->
|
||||||
|
rs.next()
|
||||||
|
val count = rs.getInt(1)
|
||||||
|
ret = count == 0
|
||||||
|
if (ret) {
|
||||||
|
println("All ok!")
|
||||||
|
} else {
|
||||||
|
println("$count errors detected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt.executeUpdate(
|
||||||
|
"""
|
||||||
|
update telemetry t
|
||||||
|
join err e on e.id=t.id
|
||||||
|
set t.defective=true, t.defective_reason=e.reason
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
stmt.executeUpdate("drop temporary table err")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
conn.rollback()
|
||||||
|
} finally {
|
||||||
|
conn.autoCommit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Verification complete")
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
package org.pavloveugene.iot.backend.services
|
package org.pavloveugene.iot.backend.services
|
||||||
|
|
||||||
import MessageDto
|
import MessageDto
|
||||||
|
import io.ktor.server.websocket.WebSocketServerSession
|
||||||
|
import io.ktor.websocket.WebSocketSession
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.pavloveugene.iot.backend.db.Database
|
import org.pavloveugene.iot.backend.db.Database
|
||||||
|
import org.pavloveugene.iot.backend.dto.EventDto
|
||||||
import org.pavloveugene.iot.backend.dto.TelemetryDto
|
import org.pavloveugene.iot.backend.dto.TelemetryDto
|
||||||
|
|
||||||
class ProtocolService(
|
class ProtocolService(
|
||||||
private val json: Json
|
private val json: Json
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun handleMessage(msg: MessageDto) {
|
fun handleMessage(msg: MessageDto, session: WebSocketSession) {
|
||||||
when (msg.t) {
|
when (msg.t) {
|
||||||
MessageType.TELEMETRY -> {
|
MessageType.TELEMETRY -> {
|
||||||
handleTelemetry(msg)
|
handleTelemetry(msg)
|
||||||
@@ -20,6 +23,7 @@ class ProtocolService(
|
|||||||
MessageType.EVENT -> {
|
MessageType.EVENT -> {
|
||||||
println("=== EVENT ===")
|
println("=== EVENT ===")
|
||||||
println(msg.p)
|
println(msg.p)
|
||||||
|
handleEvent(msg, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageType.COMMAND -> {
|
MessageType.COMMAND -> {
|
||||||
@@ -29,6 +33,27 @@ class ProtocolService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleEvent(msg: MessageDto, session: WebSocketSession) {
|
||||||
|
val payload = json.decodeFromJsonElement(EventDto.serializer(), msg.p)
|
||||||
|
when (payload.type) {
|
||||||
|
"HB" -> {
|
||||||
|
println("=== HB devId = ${msg.d} ===")
|
||||||
|
|
||||||
|
val connection = DeviceConnections.get(msg.d)
|
||||||
|
if (connection == null) {
|
||||||
|
DeviceConnections.register(
|
||||||
|
msg.d, DeviceConnection(
|
||||||
|
session = session,
|
||||||
|
lastSeen = System.currentTimeMillis()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
connection.lastSeen = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun handleTelemetry(msg: MessageDto) {
|
fun handleTelemetry(msg: MessageDto) {
|
||||||
val ds = Database.dataSource
|
val ds = Database.dataSource
|
||||||
|
|
||||||
@@ -37,19 +62,23 @@ class ProtocolService(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// ensure device
|
// ensure device
|
||||||
conn.prepareStatement("""
|
conn.prepareStatement(
|
||||||
|
"""
|
||||||
INSERT INTO devices(id)
|
INSERT INTO devices(id)
|
||||||
VALUES (?)
|
VALUES (?)
|
||||||
ON DUPLICATE KEY UPDATE id = id
|
ON DUPLICATE KEY UPDATE id = id
|
||||||
""").use {
|
"""
|
||||||
|
).use {
|
||||||
it.setLong(1, msg.d.toLong())
|
it.setLong(1, msg.d.toLong())
|
||||||
it.executeUpdate()
|
it.executeUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// check enabled
|
// check enabled
|
||||||
val isEnabled = conn.prepareStatement("""
|
val isEnabled = conn.prepareStatement(
|
||||||
|
"""
|
||||||
SELECT is_enabled FROM devices WHERE id = ?
|
SELECT is_enabled FROM devices WHERE id = ?
|
||||||
""").use {
|
"""
|
||||||
|
).use {
|
||||||
it.setLong(1, msg.d.toLong())
|
it.setLong(1, msg.d.toLong())
|
||||||
val rs = it.executeQuery()
|
val rs = it.executeQuery()
|
||||||
if (rs.next()) rs.getBoolean(1) else false
|
if (rs.next()) rs.getBoolean(1) else false
|
||||||
@@ -70,18 +99,21 @@ class ProtocolService(
|
|||||||
println("values=${payload.v}")
|
println("values=${payload.v}")
|
||||||
|
|
||||||
// insert telemetry
|
// insert telemetry
|
||||||
conn.prepareStatement("""
|
conn.prepareStatement(
|
||||||
|
"""
|
||||||
INSERT INTO telemetry(
|
INSERT INTO telemetry(
|
||||||
device_id, ts, metric, source, unit, payload
|
device_id, ts, metric, source, unit, payload
|
||||||
) VALUES (?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?)
|
||||||
""").use {
|
"""
|
||||||
|
).use {
|
||||||
it.setLong(1, msg.d.toLong())
|
it.setLong(1, msg.d.toLong())
|
||||||
it.setLong(2, msg.ts)
|
it.setLong(2, msg.ts)
|
||||||
it.setString(3, payload.m)
|
it.setString(3, payload.m)
|
||||||
it.setString(4, payload.s)
|
it.setString(4, payload.s)
|
||||||
it.setString(5, payload.u)
|
it.setString(5, payload.u)
|
||||||
|
|
||||||
val payloadJson = json.encodeToString( ListSerializer(ListSerializer(Double.serializer())),payload.v)
|
val payloadJson =
|
||||||
|
json.encodeToString(ListSerializer(ListSerializer(Double.serializer())), payload.v)
|
||||||
it.setString(6, payloadJson)
|
it.setString(6, payloadJson)
|
||||||
|
|
||||||
it.executeUpdate()
|
it.executeUpdate()
|
||||||
|
|||||||
BIN
contract/ingest.zip
Normal file
BIN
contract/ingest.zip
Normal file
Binary file not shown.
29
contract/ingest/command.schema.json
Normal file
29
contract/ingest/command.schema.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"$id": "contract/ingest/command.schema.json",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["t"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
|
||||||
|
"properties": {
|
||||||
|
"t": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["fw"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": { "t": { "const": "fw" } }
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"required": ["u", "s"],
|
||||||
|
"properties": {
|
||||||
|
"u": { "type": "string", "format": "uri" },
|
||||||
|
"s": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,38 +1,11 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"type": "object",
|
||||||
"$id": "contract/ingest/event.schema.json",
|
"required": ["type"],
|
||||||
"allOf": [
|
"properties": {
|
||||||
{ "$ref": "contract/ingest/common.schema.json" },
|
"type": {
|
||||||
{
|
"type": "string",
|
||||||
"type": "object",
|
"enum": ["hb"]
|
||||||
"properties": {
|
|
||||||
"type": {
|
|
||||||
"const": "event"
|
|
||||||
},
|
|
||||||
"payload": {
|
|
||||||
"type": "object",
|
|
||||||
"required": ["name"],
|
|
||||||
"additionalProperties": false,
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1,
|
|
||||||
"description": "Event identifier, e.g. device.overheat"
|
|
||||||
},
|
|
||||||
"severity": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["debug", "info", "warn", "error", "fatal"]
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
20
db/migrations/V2__telemetry_data.sql
Normal file
20
db/migrations/V2__telemetry_data.sql
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
CREATE TABLE telemetry_data (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
|
||||||
|
device_id BIGINT NOT NULL,
|
||||||
|
ts BIGINT NOT NULL,
|
||||||
|
|
||||||
|
metric VARCHAR(32) NOT NULL,
|
||||||
|
source VARCHAR(64) NOT NULL,
|
||||||
|
|
||||||
|
value DOUBLE NOT NULL,
|
||||||
|
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
INDEX idx_device_metric_ts (device_id, metric, ts),
|
||||||
|
INDEX idx_ts (ts),
|
||||||
|
|
||||||
|
FOREIGN KEY (device_id) REFERENCES devices(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_processed ON telemetry(processed);
|
||||||
2
db/migrations/V3__telemetry_processed_index.sql
Normal file
2
db/migrations/V3__telemetry_processed_index.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
CREATE INDEX idx_telemetry_processed_id
|
||||||
|
ON telemetry (processed, id);
|
||||||
16
db/migrations/V4__telemetry_units.sql
Normal file
16
db/migrations/V4__telemetry_units.sql
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
alter TABLE telemetry
|
||||||
|
add defective boolean default false,
|
||||||
|
add defective_reason varchar(64);
|
||||||
|
|
||||||
|
alter TABLE telemetry_data ADD
|
||||||
|
unit varchar (16);
|
||||||
|
|
||||||
|
create table units (
|
||||||
|
unit varchar(16) not null,
|
||||||
|
target_unit varchar(16) not null,
|
||||||
|
multiplier double not null,
|
||||||
|
primary key (unit)
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into units (unit, target_unit, multiplier)
|
||||||
|
values ('c_x100', 'c', 0.01);
|
||||||
24
db/migrations/V5__firmware_updates.sql
Normal file
24
db/migrations/V5__firmware_updates.sql
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
create table firmware (
|
||||||
|
id int not null auto_increment,
|
||||||
|
device_id bigint not null,
|
||||||
|
version int not null,
|
||||||
|
path varchar(4096) not null,
|
||||||
|
sha256 char(64),
|
||||||
|
size int,
|
||||||
|
uploaded_at timestamp not null default current_timestamp,
|
||||||
|
primary key (id),
|
||||||
|
foreign key (device_id) references devices(id),
|
||||||
|
|
||||||
|
unique(device_id, version)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table firmware_updates (
|
||||||
|
id int not null auto_increment,
|
||||||
|
firmware_id int not null,
|
||||||
|
status varchar(32) not null, -- pending / sent / applied / failed
|
||||||
|
requested_at timestamp not null default current_timestamp,
|
||||||
|
applied_at timestamp null,
|
||||||
|
|
||||||
|
primary key (id),
|
||||||
|
foreign key (firmware_id) references firmware(id)
|
||||||
|
);
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
# CMakeLists in this exact order for cmake to work correctly
|
# CMakeLists in this exact order for cmake to work correctly
|
||||||
cmake_minimum_required(VERSION 3.22)
|
cmake_minimum_required(VERSION 3.22)
|
||||||
|
|
||||||
|
file(READ "${CMAKE_SOURCE_DIR}/version.txt" PROJECT_VER)
|
||||||
|
string(STRIP "${PROJECT_VER}" PROJECT_VER)
|
||||||
|
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
project(esp32)
|
project(esp32)
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,24 @@ dependencies:
|
|||||||
registry_url: https://components.espressif.com/
|
registry_url: https://components.espressif.com/
|
||||||
type: service
|
type: service
|
||||||
version: 1.6.1
|
version: 1.6.1
|
||||||
|
espressif/onewire_bus:
|
||||||
|
component_hash: d709015ba466095259228521cf1bad9c0cdaaa42a92ea5d9c88ec6c28ae89e9b
|
||||||
|
dependencies:
|
||||||
|
- name: idf
|
||||||
|
require: private
|
||||||
|
version: '>=5.0'
|
||||||
|
source:
|
||||||
|
registry_url: https://components.espressif.com/
|
||||||
|
type: service
|
||||||
|
version: 1.1.0
|
||||||
idf:
|
idf:
|
||||||
source:
|
source:
|
||||||
type: idf
|
type: idf
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
direct_dependencies:
|
direct_dependencies:
|
||||||
- espressif/esp_websocket_client
|
- espressif/esp_websocket_client
|
||||||
|
- espressif/onewire_bus
|
||||||
- idf
|
- idf
|
||||||
manifest_hash: 68800db522a823ce0f8709ffb9c800760892c2d5889621f1cd9804dc45ae76f9
|
manifest_hash: 13e7ecd84ccc03ee20f4d74c327e5e03dc83702693f2188053e8b384ceb1adac
|
||||||
target: esp32
|
target: esp32
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
|
|||||||
BIN
esp32/esp32.zip
Normal file
BIN
esp32/esp32.zip
Normal file
Binary file not shown.
@@ -1,8 +1,30 @@
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "main.cpp" "protocol.cpp" "wifi_manager.cpp" "system_init.cpp" "time_sync.cpp" "adc_reader.cpp" "ringbuf.cpp" "sampler_task.cpp" "sender_task.cpp"
|
SRCS "main.cpp"
|
||||||
|
"protocol.cpp"
|
||||||
|
"wifi_manager.cpp"
|
||||||
|
"system_init.cpp"
|
||||||
|
"time_sync.cpp"
|
||||||
|
"adc_reader.cpp"
|
||||||
|
"ringbuf.cpp"
|
||||||
|
"sampler_task.cpp"
|
||||||
|
"sender_task.cpp"
|
||||||
"ws.cpp"
|
"ws.cpp"
|
||||||
|
"gpio_init.cpp"
|
||||||
|
"ds18b20.cpp"
|
||||||
|
"heartbeat_task.cpp"
|
||||||
|
"fw_command.cpp"
|
||||||
|
"json_utils.cpp"
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
REQUIRES nvs_flash esp_wifi esp_netif freertos log cjson esp_timer esp_adc esp_websocket_client
|
REQUIRES nvs_flash
|
||||||
|
esp_wifi
|
||||||
|
esp_netif
|
||||||
|
freertos
|
||||||
|
log
|
||||||
|
esp_timer
|
||||||
|
esp_adc
|
||||||
|
esp_websocket_client
|
||||||
|
driver
|
||||||
|
esp_driver_gpio
|
||||||
)
|
)
|
||||||
# добавляем кастомный Kconfig
|
# добавляем кастомный Kconfig
|
||||||
set(COMPONENT_KCONFIG "Kconfig")
|
set(COMPONENT_KCONFIG "Kconfig")
|
||||||
85
esp32/main/ds18b20.cpp
Normal file
85
esp32/main/ds18b20.cpp
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#include "ds18b20.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "onewire_bus.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *TAG = "ds18b20";
|
||||||
|
|
||||||
|
static onewire_bus_handle_t bus;
|
||||||
|
|
||||||
|
uint8_t ds_crc8(const uint8_t *data, int len)
|
||||||
|
{
|
||||||
|
uint8_t crc = 0;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
uint8_t inbyte = data[i];
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
uint8_t mix = (crc ^ inbyte) & 0x01;
|
||||||
|
crc >>= 1;
|
||||||
|
if (mix) crc ^= 0x8C;
|
||||||
|
inbyte >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ds18b20_init(gpio_num_t pin)
|
||||||
|
{
|
||||||
|
onewire_bus_config_t bus_config = {};
|
||||||
|
bus_config.bus_gpio_num = pin;
|
||||||
|
|
||||||
|
onewire_bus_rmt_config_t rmt_config = {};
|
||||||
|
rmt_config.max_rx_bytes = 10;
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(onewire_new_bus_rmt(&bus_config, &rmt_config, &bus));
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "OneWire bus initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
float ds18b20_read()
|
||||||
|
{
|
||||||
|
uint8_t data[9];
|
||||||
|
uint8_t buf[4] = {0x4E, 0, 0, 0x3F}; // 10-bit (0x3F)
|
||||||
|
// reset
|
||||||
|
ESP_ERROR_CHECK(onewire_bus_reset(bus));
|
||||||
|
|
||||||
|
// SKIP ROM (один датчик)
|
||||||
|
uint8_t cmd = 0xCC;
|
||||||
|
ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, &cmd, 1));
|
||||||
|
//ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, buf, 4));
|
||||||
|
// CONVERT T
|
||||||
|
cmd = 0x44;
|
||||||
|
ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, &cmd, 1));
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(750));
|
||||||
|
|
||||||
|
// reset снова
|
||||||
|
ESP_ERROR_CHECK(onewire_bus_reset(bus));
|
||||||
|
|
||||||
|
// SKIP ROM
|
||||||
|
cmd = 0xCC;
|
||||||
|
ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, &cmd, 1));
|
||||||
|
|
||||||
|
// READ SCRATCHPAD
|
||||||
|
cmd = 0xBE;
|
||||||
|
ESP_ERROR_CHECK(onewire_bus_write_bytes(bus, &cmd, 1));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(onewire_bus_read_bytes(bus, data, 9));
|
||||||
|
|
||||||
|
ESP_LOGI(TAG,
|
||||||
|
"RAW: %02X %02X %02X %02X %02X %02X %02X %02X | CRC %02X",
|
||||||
|
data[0], data[1], data[2], data[3],
|
||||||
|
data[4], data[5], data[6], data[7], data[8]);
|
||||||
|
|
||||||
|
uint8_t crc = ds_crc8(data, 8);
|
||||||
|
if (crc != data[8]) {
|
||||||
|
ESP_LOGW(TAG, "CRC error");
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t raw = (data[1] << 8) | data[0];
|
||||||
|
return raw / 16.0f;
|
||||||
|
}
|
||||||
|
|
||||||
6
esp32/main/ds18b20.h
Normal file
6
esp32/main/ds18b20.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
|
||||||
|
void ds18b20_init(gpio_num_t pin);
|
||||||
|
float ds18b20_read();
|
||||||
18
esp32/main/fw_command.cpp
Normal file
18
esp32/main/fw_command.cpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// Created by eugene on 22.04.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "fw_command.h"
|
||||||
|
#include "json_utils.h"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
bool parse_fw_command(const char *msg, fw_cmd_t *out) {
|
||||||
|
if (!strstr(msg, "\"t\":\"c\"")) return false;
|
||||||
|
if (!strstr(msg, "\"t\":\"fw\"")) return false;
|
||||||
|
|
||||||
|
if (!json_get_string(msg, "\"u\"", out->url, sizeof(out->url))) return false;
|
||||||
|
if (!json_get_string(msg, "\"s\"", out->sha256, sizeof(out->sha256))) return false;
|
||||||
|
|
||||||
|
out->is_fw = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
9
esp32/main/fw_command.h
Normal file
9
esp32/main/fw_command.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool is_fw;
|
||||||
|
char url[256];
|
||||||
|
char sha256[65]; // 64 + \0
|
||||||
|
} fw_cmd_t;
|
||||||
|
|
||||||
|
bool parse_fw_command(const char *msg, fw_cmd_t *out);
|
||||||
20
esp32/main/gpio_init.cpp
Normal file
20
esp32/main/gpio_init.cpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#include "gpio_init.h"
|
||||||
|
#include "hal/gpio_types.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
|
||||||
|
#define PIN_OUT GPIO_NUM_22
|
||||||
|
|
||||||
|
void init_gpio()
|
||||||
|
{
|
||||||
|
gpio_config_t io_conf = {
|
||||||
|
.pin_bit_mask = (1ULL << PIN_OUT),
|
||||||
|
.mode = GPIO_MODE_OUTPUT,
|
||||||
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_DISABLE
|
||||||
|
};
|
||||||
|
|
||||||
|
gpio_config(&io_conf);
|
||||||
|
|
||||||
|
gpio_set_level(PIN_OUT, 1); // HIGH ≈ 3.3В
|
||||||
|
}
|
||||||
3
esp32/main/gpio_init.h
Normal file
3
esp32/main/gpio_init.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void init_gpio();
|
||||||
31
esp32/main/heartbeat_task.cpp
Normal file
31
esp32/main/heartbeat_task.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#include "heartbeat_task.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "protocol.h"
|
||||||
|
#include <time.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *TAG = "heartbeat";
|
||||||
|
|
||||||
|
static void heartbeat_task(void *arg)
|
||||||
|
{
|
||||||
|
while (1) {
|
||||||
|
if (protocol_is_connected()) {
|
||||||
|
|
||||||
|
time_t now;
|
||||||
|
time(&now);
|
||||||
|
|
||||||
|
protocol_send_event_hb((uint32_t)now);
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10000)); // 10 сек
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void heartbeat_task_start()
|
||||||
|
{
|
||||||
|
xTaskCreate(heartbeat_task, "heartbeat", 4096, NULL, 5, NULL);
|
||||||
|
}
|
||||||
3
esp32/main/heartbeat_task.h
Normal file
3
esp32/main/heartbeat_task.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void heartbeat_task_start(void);
|
||||||
@@ -15,3 +15,4 @@ dependencies:
|
|||||||
# # All dependencies of `main` are public by default.
|
# # All dependencies of `main` are public by default.
|
||||||
# public: true
|
# public: true
|
||||||
espressif/esp_websocket_client: '*'
|
espressif/esp_websocket_client: '*'
|
||||||
|
espressif/onewire_bus: '*'
|
||||||
30
esp32/main/json_utils.cpp
Normal file
30
esp32/main/json_utils.cpp
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#include <cstring>
|
||||||
|
#include <cstddef>
|
||||||
|
#include "json_utils.h"
|
||||||
|
|
||||||
|
bool json_get_string(const char *json, const char *key, char *out, size_t out_size) {
|
||||||
|
const char *k = strstr(json, key);
|
||||||
|
if (!k) return false;
|
||||||
|
|
||||||
|
const char *start = strchr(k, ':');
|
||||||
|
if (!start) return false;
|
||||||
|
|
||||||
|
start++;
|
||||||
|
|
||||||
|
// найти начало строки (первую кавычку)
|
||||||
|
while (*start && *start != '\"') start++;
|
||||||
|
if (!*start) return false;
|
||||||
|
|
||||||
|
start++; // после "
|
||||||
|
|
||||||
|
const char *end = strchr(start, '\"');
|
||||||
|
if (!end) return false;
|
||||||
|
|
||||||
|
size_t len = end - start;
|
||||||
|
if (len >= out_size) return false;
|
||||||
|
|
||||||
|
memcpy(out, start, len);
|
||||||
|
out[len] = '\0';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
4
esp32/main/json_utils.h
Normal file
4
esp32/main/json_utils.h
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
bool json_get_string(const char *json, const char *key, char *out, size_t out_size);
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "system_init.h"
|
#include "system_init.h"
|
||||||
|
#include "heartbeat_task.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/projdefs.h"
|
#include "freertos/projdefs.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
@@ -13,8 +14,10 @@ extern "C" void app_main(void)
|
|||||||
{
|
{
|
||||||
system_init();
|
system_init();
|
||||||
|
|
||||||
sampler_task_start();
|
//sampler_task_start();
|
||||||
sender_task_start();
|
//sender_task_start();
|
||||||
|
|
||||||
|
heartbeat_task_start();
|
||||||
|
|
||||||
system_finalize();
|
system_finalize();
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,15 @@
|
|||||||
#include "protocol.h"
|
#include "protocol.h"
|
||||||
|
#include "ws.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
#define PROTOCOL_VERSION 1
|
#define PROTOCOL_VERSION 1
|
||||||
|
|
||||||
static inline int append(char* buf, size_t size, int pos, const char* fmt, ...) {
|
static uint32_t msg_id = 1;
|
||||||
|
|
||||||
|
static inline int append(char* buf, size_t size, int pos, const char* fmt, ...)
|
||||||
|
{
|
||||||
if ((size_t)pos >= size) return -1;
|
if ((size_t)pos >= size) return -1;
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
@@ -12,7 +17,8 @@ static inline int append(char* buf, size_t size, int pos, const char* fmt, ...)
|
|||||||
int written = vsnprintf(buf + pos, size - pos, fmt, args);
|
int written = vsnprintf(buf + pos, size - pos, fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
if (written < 0 || (size_t)(pos + written) >= size) {
|
if (written < 0 || (size_t)(pos + written) >= size)
|
||||||
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +28,6 @@ static inline int append(char* buf, size_t size, int pos, const char* fmt, ...)
|
|||||||
int build_telemetry(
|
int build_telemetry(
|
||||||
char* buf,
|
char* buf,
|
||||||
size_t buf_size,
|
size_t buf_size,
|
||||||
uint32_t msg_id,
|
|
||||||
int64_t ts,
|
int64_t ts,
|
||||||
uint32_t device_id,
|
uint32_t device_id,
|
||||||
const char* metric,
|
const char* metric,
|
||||||
@@ -30,34 +35,36 @@ int build_telemetry(
|
|||||||
const char* unit,
|
const char* unit,
|
||||||
const int* values,
|
const int* values,
|
||||||
size_t count
|
size_t count
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
|
|
||||||
// --- header ---
|
// --- header ---
|
||||||
pos = append(buf, buf_size, pos,
|
pos = append(buf, buf_size, pos,
|
||||||
"{\"v\":1,\"t\":\"t\",\"id\":%lu,\"ts\":%lld,\"d\":%lu,\"p\":{",
|
"{\"v\":1,\"t\":\"t\",\"id\":%lu,\"ts\":%lld,\"d\":%lu,\"p\":{",
|
||||||
(unsigned long)msg_id,
|
(unsigned long)protocol_next_id(),
|
||||||
(long long)ts,
|
(long long)ts,
|
||||||
(unsigned long)device_id
|
(unsigned long)device_id
|
||||||
);
|
);
|
||||||
if (pos < 0) return -1;
|
if (pos < 0) return -1;
|
||||||
|
|
||||||
// --- payload meta ---
|
// --- payload meta ---
|
||||||
pos = append(buf, buf_size, pos,
|
pos = append(buf, buf_size, pos,
|
||||||
"\"m\":\"%s\",\"s\":\"%s\",\"u\":\"%s\",\"v\":[",
|
"\"m\":\"%s\",\"s\":\"%s\",\"u\":\"%s\",\"v\":[",
|
||||||
metric,
|
metric,
|
||||||
source,
|
source,
|
||||||
unit
|
unit
|
||||||
);
|
);
|
||||||
if (pos < 0) return -1;
|
if (pos < 0) return -1;
|
||||||
|
|
||||||
// --- values ---
|
// --- values ---
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count ; i ++)
|
||||||
|
{
|
||||||
pos = append(buf, buf_size, pos,
|
pos = append(buf, buf_size, pos,
|
||||||
"[%u,%d]%s",
|
"[%u,%d]%s",
|
||||||
(unsigned)i, // delta time (пока просто индекс)
|
values[i*2 + 0], // delta time
|
||||||
values[i],
|
values[i*2 + 1],
|
||||||
(i < count - 1) ? "," : ""
|
(i < count - 1) ? "," : ""
|
||||||
);
|
);
|
||||||
if (pos < 0) return -1;
|
if (pos < 0) return -1;
|
||||||
}
|
}
|
||||||
@@ -71,7 +78,7 @@ int build_telemetry(
|
|||||||
|
|
||||||
__attribute__((deprecated))
|
__attribute__((deprecated))
|
||||||
// --- TELEMETRY (single measurement) ---
|
// --- TELEMETRY (single measurement) ---
|
||||||
int build_telemetry_single(
|
int build_telemetry_single(
|
||||||
char* buf,
|
char* buf,
|
||||||
size_t buf_size,
|
size_t buf_size,
|
||||||
const char* id,
|
const char* id,
|
||||||
@@ -82,40 +89,44 @@ __attribute__((deprecated))
|
|||||||
const char* unit,
|
const char* unit,
|
||||||
const char* source,
|
const char* source,
|
||||||
int64_t m_ts_ms
|
int64_t m_ts_ms
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
|
|
||||||
// header
|
// header
|
||||||
pos = append(buf, buf_size, pos,
|
pos = append(buf, buf_size, pos,
|
||||||
"{\"v\":%d,\"id\":\"%s\",\"type\":\"telemetry\",\"ts\":%lld,"
|
"{\"v\":%d,\"id\":\"%s\",\"type\":\"telemetry\",\"ts\":%lld,"
|
||||||
"\"deviceId\":\"%s\",\"payload\":{\"measurements\":[",
|
"\"deviceId\":\"%s\",\"payload\":{\"measurements\":[",
|
||||||
PROTOCOL_VERSION,
|
PROTOCOL_VERSION,
|
||||||
id,
|
id,
|
||||||
(long long)ts_ms,
|
(long long)ts_ms,
|
||||||
device_id
|
device_id
|
||||||
);
|
);
|
||||||
if (pos < 0) return -1;
|
if (pos < 0) return -1;
|
||||||
|
|
||||||
// measurement start
|
// measurement start
|
||||||
pos = append(buf, buf_size, pos,
|
pos = append(buf, buf_size, pos,
|
||||||
"{\"metric\":\"%s\",\"value\":%.3f",
|
"{\"metric\":\"%s\",\"value\":%.3f",
|
||||||
metric,
|
metric,
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
if (pos < 0) return -1;
|
if (pos < 0) return -1;
|
||||||
|
|
||||||
// optional
|
// optional
|
||||||
if (unit) {
|
if (unit)
|
||||||
|
{
|
||||||
pos = append(buf, buf_size, pos, ",\"unit\":\"%s\"", unit);
|
pos = append(buf, buf_size, pos, ",\"unit\":\"%s\"", unit);
|
||||||
if (pos < 0) return -1;
|
if (pos < 0) return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source) {
|
if (source)
|
||||||
|
{
|
||||||
pos = append(buf, buf_size, pos, ",\"source\":\"%s\"", source);
|
pos = append(buf, buf_size, pos, ",\"source\":\"%s\"", source);
|
||||||
if (pos < 0) return -1;
|
if (pos < 0) return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_ts_ms > 0) {
|
if (m_ts_ms > 0)
|
||||||
|
{
|
||||||
pos = append(buf, buf_size, pos, ",\"ts\":%lld", (long long)m_ts_ms);
|
pos = append(buf, buf_size, pos, ",\"ts\":%lld", (long long)m_ts_ms);
|
||||||
if (pos < 0) return -1;
|
if (pos < 0) return -1;
|
||||||
}
|
}
|
||||||
@@ -138,17 +149,18 @@ int build_event(
|
|||||||
const char* name,
|
const char* name,
|
||||||
const char* severity,
|
const char* severity,
|
||||||
const char* message
|
const char* message
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
|
|
||||||
// header
|
// header
|
||||||
pos = append(buf, buf_size, pos,
|
pos = append(buf, buf_size, pos,
|
||||||
"{\"v\":%d,\"id\":\"%s\",\"type\":\"event\",\"ts\":%lld,"
|
"{\"v\":%d,\"id\":\"%s\",\"type\":\"event\",\"ts\":%lld,"
|
||||||
"\"deviceId\":\"%s\",\"payload\":{",
|
"\"deviceId\":\"%s\",\"payload\":{",
|
||||||
PROTOCOL_VERSION,
|
PROTOCOL_VERSION,
|
||||||
id,
|
id,
|
||||||
(long long)ts_ms,
|
(long long)ts_ms,
|
||||||
device_id
|
device_id
|
||||||
);
|
);
|
||||||
if (pos < 0) return -1;
|
if (pos < 0) return -1;
|
||||||
|
|
||||||
@@ -157,12 +169,14 @@ int build_event(
|
|||||||
if (pos < 0) return -1;
|
if (pos < 0) return -1;
|
||||||
|
|
||||||
// optional
|
// optional
|
||||||
if (severity) {
|
if (severity)
|
||||||
|
{
|
||||||
pos = append(buf, buf_size, pos, ",\"severity\":\"%s\"", severity);
|
pos = append(buf, buf_size, pos, ",\"severity\":\"%s\"", severity);
|
||||||
if (pos < 0) return -1;
|
if (pos < 0) return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message) {
|
if (message)
|
||||||
|
{
|
||||||
pos = append(buf, buf_size, pos, ",\"message\":\"%s\"", message);
|
pos = append(buf, buf_size, pos, ",\"message\":\"%s\"", message);
|
||||||
if (pos < 0) return -1;
|
if (pos < 0) return -1;
|
||||||
}
|
}
|
||||||
@@ -173,3 +187,29 @@ int build_event(
|
|||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t protocol_next_id()
|
||||||
|
{
|
||||||
|
return msg_id++;
|
||||||
|
};
|
||||||
|
|
||||||
|
void protocol_send_event_hb(int64_t ts)
|
||||||
|
{
|
||||||
|
// формируешь JSON строго по контракту
|
||||||
|
|
||||||
|
// пример:
|
||||||
|
char buf[128];
|
||||||
|
|
||||||
|
uint32_t id = protocol_next_id();
|
||||||
|
|
||||||
|
snprintf(buf, sizeof(buf),
|
||||||
|
"{\"v\":1,\"id\":%" PRIu32 ",\"t\":\"e\",\"ts\":%" PRIu64 ",\"d\":%u,\"p\":{\"type\":\"hb\"}}",
|
||||||
|
id, ts, CONFIG_DEVICE_ID);
|
||||||
|
|
||||||
|
ws_send(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool protocol_is_connected()
|
||||||
|
{
|
||||||
|
return ws_is_connected();
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include "ringbuf.h"
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -10,7 +12,6 @@ extern "C" {
|
|||||||
int build_telemetry(
|
int build_telemetry(
|
||||||
char* buf,
|
char* buf,
|
||||||
size_t buf_size,
|
size_t buf_size,
|
||||||
uint32_t msg_id,
|
|
||||||
int64_t ts,
|
int64_t ts,
|
||||||
uint32_t device_id,
|
uint32_t device_id,
|
||||||
const char* metric,
|
const char* metric,
|
||||||
@@ -44,6 +45,12 @@ int build_event(
|
|||||||
const char* message // optional
|
const char* message // optional
|
||||||
);
|
);
|
||||||
|
|
||||||
|
uint32_t protocol_next_id();
|
||||||
|
|
||||||
|
void protocol_send_event_hb(int64_t ts);
|
||||||
|
|
||||||
|
bool protocol_is_connected();
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -3,16 +3,18 @@
|
|||||||
void ringbuf_init(ringbuf_t *rb) {
|
void ringbuf_init(ringbuf_t *rb) {
|
||||||
rb->head = 0;
|
rb->head = 0;
|
||||||
for (int i = 0; i < RINGBUF_SIZE; i++) {
|
for (int i = 0; i < RINGBUF_SIZE; i++) {
|
||||||
rb->values[i] = 0;
|
rb->values[i].ts_ms = 0;
|
||||||
|
rb->values[i].value = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ringbuf_push(ringbuf_t *rb, int v) {
|
void ringbuf_push(ringbuf_t *rb, uint32_t ts_ms, int v) {
|
||||||
rb->values[rb->head] = v;
|
rb->values[rb->head].ts_ms = ts_ms;
|
||||||
|
rb->values[rb->head].value = v;
|
||||||
rb->head = (rb->head + 1) % RINGBUF_SIZE;
|
rb->head = (rb->head + 1) % RINGBUF_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ringbuf_copy(const ringbuf_t *rb, int *out) {
|
void ringbuf_copy(const ringbuf_t *rb, sample_t *out) {
|
||||||
int idx = rb->head;
|
int idx = rb->head;
|
||||||
|
|
||||||
for (int i = 0; i < RINGBUF_SIZE; i++) {
|
for (int i = 0; i < RINGBUF_SIZE; i++) {
|
||||||
|
|||||||
@@ -5,7 +5,12 @@
|
|||||||
#define RINGBUF_SIZE 10
|
#define RINGBUF_SIZE 10
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int values[RINGBUF_SIZE];
|
uint32_t ts_ms;
|
||||||
|
int value;
|
||||||
|
} sample_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
sample_t values[RINGBUF_SIZE];
|
||||||
int head;
|
int head;
|
||||||
} ringbuf_t;
|
} ringbuf_t;
|
||||||
|
|
||||||
@@ -13,7 +18,6 @@ typedef struct {
|
|||||||
void ringbuf_init(ringbuf_t *rb);
|
void ringbuf_init(ringbuf_t *rb);
|
||||||
|
|
||||||
// Добавить значение
|
// Добавить значение
|
||||||
void ringbuf_push(ringbuf_t *rb, int v);
|
void ringbuf_push(ringbuf_t *rb, uint32_t ts_ms, int v);
|
||||||
|
|
||||||
// Скопировать данные (в порядке времени)
|
void ringbuf_copy(const ringbuf_t *rb, sample_t *out);
|
||||||
void ringbuf_copy(const ringbuf_t *rb, int *out);
|
|
||||||
@@ -1,37 +1,43 @@
|
|||||||
#include "sampler_task.h"
|
#include "sampler_task.h"
|
||||||
#include "adc_reader.h"
|
|
||||||
#include "ringbuf.h"
|
#include "ringbuf.h"
|
||||||
|
#include "ds18b20.h" // добавим
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
// буфер объявим здесь (глобальный для простоты)
|
// буфер
|
||||||
static ringbuf_t rb;
|
static ringbuf_t rb;
|
||||||
|
|
||||||
// дать доступ другим модулям (sender потом возьмёт)
|
|
||||||
ringbuf_t* sampler_get_buffer() {
|
ringbuf_t* sampler_get_buffer() {
|
||||||
return &rb;
|
return &rb;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sampler_task(void *arg) {
|
static void sampler_task(void *arg) {
|
||||||
while (1) {
|
|
||||||
int val = adc_reader_read();
|
|
||||||
ringbuf_push(&rb, val);
|
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(100)); // 10 Гц
|
const TickType_t period = pdMS_TO_TICKS(6000);
|
||||||
|
TickType_t last_wake = xTaskGetTickCount();
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
int64_t now_ms = esp_timer_get_time() / 1000;
|
||||||
|
|
||||||
|
float temp = ds18b20_read();
|
||||||
|
ringbuf_push(&rb, now_ms, (int)(temp * 100));
|
||||||
|
|
||||||
|
vTaskDelayUntil(&last_wake, period);// 6 секунд
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sampler_task_start() {
|
void sampler_task_start() {
|
||||||
ringbuf_init(&rb);
|
ringbuf_init(&rb);
|
||||||
adc_reader_init();
|
ds18b20_init(GPIO_NUM_27); // новый драйвер
|
||||||
|
|
||||||
xTaskCreate(
|
xTaskCreate(
|
||||||
sampler_task,
|
sampler_task,
|
||||||
"sampler",
|
"sampler",
|
||||||
2048,
|
4096,
|
||||||
NULL,
|
NULL,
|
||||||
5,
|
5,
|
||||||
NULL
|
NULL
|
||||||
|
|||||||
@@ -5,54 +5,88 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
|
||||||
#include "ws.h"
|
#include "ws.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t msg_id = 1;
|
static uint32_t last_send_ms = 0;
|
||||||
|
static int64_t last_send_ts = 0;
|
||||||
|
|
||||||
static void sender_task(void *arg) {
|
static void sender_task(void* arg)
|
||||||
int data[RINGBUF_SIZE];
|
{
|
||||||
|
ringbuf_t* rb = sampler_get_buffer();
|
||||||
|
|
||||||
while (1) {
|
last_send_ts = time(NULL);
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000)); // 1 Гц
|
last_send_ms = esp_timer_get_time() / 1000;
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50000)); // 1 мин^-1
|
||||||
|
|
||||||
char buf[512];
|
char buf[512];
|
||||||
|
uint32_t now_ms = esp_timer_get_time() / 1000;
|
||||||
|
time_t now_ts = time(NULL);
|
||||||
|
|
||||||
ringbuf_t *rb = sampler_get_buffer();
|
sample_t tmp[RINGBUF_SIZE];
|
||||||
ringbuf_copy(rb, data);
|
ringbuf_copy(rb, tmp);
|
||||||
|
|
||||||
time_t now = time(NULL);
|
int out_values[RINGBUF_SIZE * 2];
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < RINGBUF_SIZE; i++)
|
||||||
|
{
|
||||||
|
if (tmp[i].ts_ms >= last_send_ms)
|
||||||
|
{
|
||||||
|
uint32_t delta = tmp[i].ts_ms - last_send_ms;
|
||||||
|
|
||||||
|
// кладём delta + value
|
||||||
|
out_values[count * 2 + 0] = delta;
|
||||||
|
out_values[count * 2 + 1] = tmp[i].value;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int len = build_telemetry(
|
int len = build_telemetry(
|
||||||
buf,
|
buf,
|
||||||
sizeof(buf),
|
sizeof(buf),
|
||||||
msg_id++,
|
last_send_ts,
|
||||||
now,
|
1, // device_id (пока захардкожен)
|
||||||
1, // device_id (пока захардкожен)
|
"t", // metric
|
||||||
"v", // metric
|
"ds18b20", // source
|
||||||
"adc35", // source
|
"c_x100", // unit
|
||||||
"raw", // unit
|
out_values,
|
||||||
data,
|
count
|
||||||
RINGBUF_SIZE
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (len > 0) {
|
if (len > 0)
|
||||||
if (ws_is_connected()) {
|
{
|
||||||
|
if (ws_is_connected())
|
||||||
|
{
|
||||||
ws_send(buf);
|
ws_send(buf);
|
||||||
} else {
|
|
||||||
printf("%s\n", buf); // fallback
|
|
||||||
}
|
}
|
||||||
} else {
|
else
|
||||||
|
{
|
||||||
|
printf("%s\n", buf); // fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
printf("build_telemetry failed\n");
|
printf("build_telemetry failed\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
last_send_ms = now_ms;
|
||||||
|
last_send_ts = now_ts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sender_task_start() {
|
void sender_task_start()
|
||||||
|
{
|
||||||
xTaskCreate(
|
xTaskCreate(
|
||||||
sender_task,
|
sender_task,
|
||||||
"sender",
|
"sender",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "esp_event.h"
|
#include "esp_event.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
#include "ws.h"
|
#include "ws.h"
|
||||||
|
#include "gpio_init.h"
|
||||||
|
|
||||||
static const char* TAG = "system";
|
static const char* TAG = "system";
|
||||||
|
|
||||||
@@ -14,6 +15,8 @@ void system_init()
|
|||||||
{
|
{
|
||||||
ESP_LOGI("SYSTEM", "Initializing system...");
|
ESP_LOGI("SYSTEM", "Initializing system...");
|
||||||
|
|
||||||
|
init_gpio();
|
||||||
|
|
||||||
init_wifi(CONFIG_WIFI_SSID, CONFIG_WIFI_PASS); // Запуск WiFI
|
init_wifi(CONFIG_WIFI_SSID, CONFIG_WIFI_PASS); // Запуск WiFI
|
||||||
|
|
||||||
init_time(); // Установка времени
|
init_time(); // Установка времени
|
||||||
|
|||||||
@@ -3,19 +3,20 @@
|
|||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
|
#include "fw_command.h"
|
||||||
|
|
||||||
static const char* TAG = "WS";
|
static const char* TAG = "WS";
|
||||||
|
|
||||||
static esp_websocket_client_handle_t client = nullptr;
|
static esp_websocket_client_handle_t client = nullptr;
|
||||||
static bool connected = false;
|
static bool connected = false;
|
||||||
|
|
||||||
static void ws_event_handler(void *handler_args,
|
static void ws_event_handler(void* handler_args,
|
||||||
esp_event_base_t base,
|
esp_event_base_t base,
|
||||||
int32_t event_id,
|
int32_t event_id,
|
||||||
void *event_data)
|
void* event_data)
|
||||||
{
|
{
|
||||||
switch (event_id) {
|
switch (event_id)
|
||||||
|
{
|
||||||
case WEBSOCKET_EVENT_CONNECTED:
|
case WEBSOCKET_EVENT_CONNECTED:
|
||||||
connected = true;
|
connected = true;
|
||||||
ESP_LOGI(TAG, "Connected");
|
ESP_LOGI(TAG, "Connected");
|
||||||
@@ -31,11 +32,36 @@ static void ws_event_handler(void *handler_args,
|
|||||||
ESP_LOGE(TAG, "Error");
|
ESP_LOGE(TAG, "Error");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WEBSOCKET_EVENT_DATA: {
|
case WEBSOCKET_EVENT_DATA:
|
||||||
auto *data = (esp_websocket_event_data_t *)event_data;
|
{
|
||||||
|
auto* data = (esp_websocket_event_data_t*)event_data;
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Recv: %.*s", data->data_len, (char*)data->data_ptr);
|
ESP_LOGI(TAG, "Recv: %.*s", data->data_len, (char*)data->data_ptr);
|
||||||
|
|
||||||
|
// ⚠️ делаем null-terminated копию
|
||||||
|
char buf[512]; // подбери размер под свой максимум
|
||||||
|
int len = data->data_len;
|
||||||
|
|
||||||
|
if (len >= sizeof(buf)) {
|
||||||
|
ESP_LOGW(TAG, "Message too large");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(buf, data->data_ptr, len);
|
||||||
|
buf[len] = '\0';
|
||||||
|
|
||||||
|
fw_cmd_t cmd {};
|
||||||
|
|
||||||
|
if (parse_fw_command(buf, &cmd)) {
|
||||||
|
ESP_LOGI(TAG, "FW command received");
|
||||||
|
ESP_LOGI(TAG, "URL: %s", cmd.url);
|
||||||
|
ESP_LOGI(TAG, "SHA256: %s", cmd.sha256);
|
||||||
|
|
||||||
|
// пока просто лог, без OTA
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +78,8 @@ void ws_init(const char* uri)
|
|||||||
|
|
||||||
void ws_start()
|
void ws_start()
|
||||||
{
|
{
|
||||||
if (client) {
|
if (client)
|
||||||
|
{
|
||||||
esp_websocket_client_start(client);
|
esp_websocket_client_start(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +91,8 @@ bool ws_is_connected()
|
|||||||
|
|
||||||
void ws_send(const char* data)
|
void ws_send(const char* data)
|
||||||
{
|
{
|
||||||
if (connected && client) {
|
if (connected && client)
|
||||||
|
{
|
||||||
esp_websocket_client_send_text(client, data, strlen(data), portMAX_DELAY);
|
esp_websocket_client_send_text(client, data, strlen(data), portMAX_DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
onewire_bus/test_apps:
|
||||||
|
disable:
|
||||||
|
- if: CONFIG_NAME == "rmt" and SOC_RMT_SUPPORTED != 1
|
||||||
|
reason: RMT backend variant requires SOC RMT support
|
||||||
|
- if: CONFIG_NAME == "uart" and SOC_UART_SUPPORTED != 1
|
||||||
|
reason: UART backend variant requires SOC UART support
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
d709015ba466095259228521cf1bad9c0cdaaa42a92ea5d9c88ec6c28ae89e9b
|
||||||
19
esp32/managed_components/espressif__onewire_bus/CHANGELOG.md
Normal file
19
esp32/managed_components/espressif__onewire_bus/CHANGELOG.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
- Add UART backend support for 1-Wire bus (`onewire_new_bus_uart`) alongside the existing RMT backend.
|
||||||
|
|
||||||
|
## 1.0.4
|
||||||
|
|
||||||
|
- Support `en_pull_up` config option in `onewire_bus_config_t`, which can enable the internal pull-up resistor on the GPIO pin used for the one-wire bus. This is useful when using a GPIO pin that does not have a pull-up resistor connected externally.
|
||||||
|
|
||||||
|
## 1.0.3
|
||||||
|
|
||||||
|
- Improve the driver to support esp-idf v6.0
|
||||||
|
|
||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
- raise recovery time to support more sensor on longer wire (d0b2b52)
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial driver version, with the RMT driver as backend controller
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":"1.0","algorithm":"sha256","created_at":"2026-04-02T08:31:31.921867+00:00","files":[{"path":".build-test-rules.yml","size":272,"hash":"d34faa08f404a108c17bcaf9709561dbc978c4f4fea0b912857758064430e069"},{"path":"CHANGELOG.md","size":591,"hash":"6be143366596176ad9f2d17932154117ef861918881e5abcfce678d55331e361"},{"path":"CMakeLists.txt","size":742,"hash":"8f26f2f876456b7533884d02b72dfee3481fe8617420c34cbfacf8c03dbc187a"},{"path":"LICENSE","size":11358,"hash":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30"},{"path":"README.md","size":675,"hash":"ebc34c6d6383f828a433fced21289cc24a1248a0383c240aabb95912170c1cc6"},{"path":"idf_component.yml","size":386,"hash":"4054b22a198bae53f75aeacced4a6577dbd067df3d07567f0ff3930164d2bb10"},{"path":"include/onewire_bus.h","size":2993,"hash":"39804a54186091e6f6b17aba786d5e78b70e9ad8e9bbd79cf31989f282dada21"},{"path":"include/onewire_bus_impl_rmt.h","size":1323,"hash":"a8aeebf3cd5f23ddd00c76ec43dfd6f1a30eee819814fb0c0d50a1e292834511"},{"path":"include/onewire_bus_impl_uart.h","size":1253,"hash":"403a3cb182173148f7999dbfbd800b9baab80f51c3f00607b1ccfc18a91af108"},{"path":"include/onewire_cmd.h","size":356,"hash":"f0de787a3337b59e6a85c8a245ef5b56710830233ceeabf5fe59d1c0a0c50b38"},{"path":"include/onewire_crc.h","size":584,"hash":"9db2a7b437a6f72a0711698c9fad5ceca60458e828557cc38a3c8fc217fd5e06"},{"path":"include/onewire_device.h","size":1799,"hash":"f6b8202100de34d4b102953839e10df33b7850c35c599fb1d5bcf33ec1305773"},{"path":"include/onewire_types.h","size":1107,"hash":"bbfbaffac8df97fe53fcf42550f63dad9e3c29fac3fb26a03d735075eed85b31"},{"path":"interface/onewire_bus_interface.h","size":3196,"hash":"b03cea2218f99845b4486b7112760e08e4b7eb6b6dcc238f8d745afc593eae19"},{"path":"src/onewire_bus_api.c","size":1512,"hash":"894fdc649d552fe090c19bd3eb9e1433db71f95b8455b86faf5463636905ab3e"},{"path":"src/onewire_bus_impl_rmt.c","size":21406,"hash":"2e48338edb5e72065c239863811e387adc7164c2bb0f344bbca7a8e4ab07f624"},{"path":"src/onewire_bus_impl_uart.c","size":13129,"hash":"00f18bf1c3ea011c0952ca8dbf898a74094985932d7da2b4c8ff15ad99244576"},{"path":"src/onewire_crc.c","size":2193,"hash":"06a7ce5ffffbd1cbf4de35155cba2d5b903f718a0fbc3478265c09a4458a4ce0"},{"path":"src/onewire_device.c","size":4934,"hash":"3612afec5795eb9f47adef0b340c33c8584dde4d66d9581621ae203a6e9e031a"},{"path":"test_apps/CMakeLists.txt","size":135,"hash":"4035fd2167868739138e691d3080616b9d3b5c69adf567fbd33fb74b36a143ce"},{"path":"test_apps/pytest_onewire_bus.py","size":722,"hash":"c00451c79c16e3e3010045c21a21cb8c8e82217b898946061a4d133d096eca11"},{"path":"test_apps/sdkconfig.ci.rmt","size":75,"hash":"e8b859d77cdf1dc4125c5cdb4681dab1219db72c845f3f1b62f6d06397d804fa"},{"path":"test_apps/sdkconfig.ci.uart","size":75,"hash":"8413491b74b639e0a4e4d29968989d8434ed44bd225664352b631b7794c56320"},{"path":"test_apps/main/CMakeLists.txt","size":127,"hash":"2c724c0cf0fe496ed895d2360a281ad0c81b398c43cdfccb290c5b627c852f9a"},{"path":"test_apps/main/Kconfig.projbuild","size":1782,"hash":"70ccb326b523ed810f1597fe51c8b12a120156bd431552d0106c4f8e468c10c1"},{"path":"test_apps/main/idf_component.yml","size":83,"hash":"76ba47c24c863880ee8e6667ccde3ca1b68454993f31fd45755d5faa2ab83edc"},{"path":"test_apps/main/onewire_bus_test.c","size":2906,"hash":"f25f3a330bcc50fb30da60470c38401b88a3858415abe56668cf386e4f49a2d4"}]}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
set(srcs "src/onewire_bus_api.c"
|
||||||
|
"src/onewire_crc.c"
|
||||||
|
"src/onewire_device.c")
|
||||||
|
|
||||||
|
if(CONFIG_SOC_RMT_SUPPORTED)
|
||||||
|
list(APPEND srcs "src/onewire_bus_impl_rmt.c")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CONFIG_SOC_UART_SUPPORTED)
|
||||||
|
list(APPEND srcs "src/onewire_bus_impl_uart.c")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(priv_requires)
|
||||||
|
# Starting from esp-idf v5.3, the peripheral drivers are in separate components
|
||||||
|
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.3")
|
||||||
|
list(APPEND priv_requires "esp_driver_rmt" "esp_driver_uart" "esp_driver_gpio")
|
||||||
|
else()
|
||||||
|
list(APPEND priv_requires "driver")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${srcs}
|
||||||
|
INCLUDE_DIRS "include" "interface"
|
||||||
|
PRIV_REQUIRES ${priv_requires})
|
||||||
202
esp32/managed_components/espressif__onewire_bus/LICENSE
Normal file
202
esp32/managed_components/espressif__onewire_bus/LICENSE
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
||||||
13
esp32/managed_components/espressif__onewire_bus/README.md
Normal file
13
esp32/managed_components/espressif__onewire_bus/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Dallas 1-Wire Bus Driver
|
||||||
|
|
||||||
|
[](https://components.espressif.com/components/espressif/onewire_bus)
|
||||||
|
|
||||||
|
This directory contains an implementation for Dallas 1-Wire bus by different peripherals.
|
||||||
|
The following low-level backends are currently supported:
|
||||||
|
|
||||||
|
- RMT backend (`onewire_new_bus_rmt`)
|
||||||
|
- UART backend (`onewire_new_bus_uart`)
|
||||||
|
|
||||||
|
## Appendix
|
||||||
|
|
||||||
|
* [DS18B20 device driver based on the 1-Wire Bus driver](https://components.espressif.com/components/espressif/ds18b20) and the [DS18B20 Example](https://github.com/espressif/esp-bsp/tree/master/components/ds18b20/examples/ds18b20-read)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
dependencies:
|
||||||
|
idf: '>=5.0'
|
||||||
|
description: Driver for Dallas 1-Wire bus
|
||||||
|
issues: https://github.com/espressif/idf-extra-components/issues
|
||||||
|
repository: git://github.com/espressif/idf-extra-components.git
|
||||||
|
repository_info:
|
||||||
|
commit_sha: bd6b21799cb9034e050a41b2c299b52a7e71be83
|
||||||
|
path: onewire_bus
|
||||||
|
url: https://github.com/espressif/idf-extra-components/tree/master/onewire_bus
|
||||||
|
version: 1.1.0
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
#include "onewire_types.h"
|
||||||
|
#if SOC_RMT_SUPPORTED
|
||||||
|
#include "onewire_bus_impl_rmt.h"
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_SUPPORTED
|
||||||
|
#include "onewire_bus_impl_uart.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write bytes to 1-wire bus
|
||||||
|
*
|
||||||
|
* @param[in] bus 1-Wire bus handle
|
||||||
|
* @param[in] tx_data pointer to data to be sent
|
||||||
|
* @param[in] tx_data_size size of data to be sent, in bytes
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Write bytes to 1-Wire bus successfully
|
||||||
|
* - ESP_ERR_INVALID_ARG: Write bytes to 1-Wire bus failed because of invalid argument
|
||||||
|
* - ESP_FAIL: Write bytes to 1-Wire bus failed because of other errors
|
||||||
|
*/
|
||||||
|
esp_err_t onewire_bus_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read bytes from 1-wire bus
|
||||||
|
*
|
||||||
|
* @param[in] bus 1-wire bus handle
|
||||||
|
* @param[out] rx_buf pointer to buffer to store received data
|
||||||
|
* @param[in] rx_buf_size size of buffer to store received data, in bytes
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Read bytes from 1-Wire bus successfully
|
||||||
|
* - ESP_ERR_INVALID_ARG: Read bytes from 1-Wire bus failed because of invalid argument
|
||||||
|
* - ESP_FAIL: Read bytes from 1-Wire bus failed because of other errors
|
||||||
|
*/
|
||||||
|
esp_err_t onewire_bus_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a bit to 1-wire bus, this is a blocking function
|
||||||
|
*
|
||||||
|
* @param[in] handle 1-wire bus handle
|
||||||
|
* @param[in] tx_bit bit to transmit, 0 for zero bit, other for one bit
|
||||||
|
* @return
|
||||||
|
* - ESP_OK Write bit to 1-wire bus successfully.
|
||||||
|
* - ESP_ERR_INVALID_ARG Invalid argument.
|
||||||
|
*/
|
||||||
|
esp_err_t onewire_bus_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read a bit from 1-wire bus
|
||||||
|
*
|
||||||
|
* @param[in] handle 1-wire bus handle
|
||||||
|
* @param[out] rx_bit received bit, 0 for zero bit, 1 for one bit
|
||||||
|
* @return
|
||||||
|
* - ESP_OK Read bit from 1-wire bus successfully.
|
||||||
|
* - ESP_ERR_INVALID_ARG Invalid argument.
|
||||||
|
*/
|
||||||
|
esp_err_t onewire_bus_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send reset pulse to the bus, and check if there are devices attached to the bus
|
||||||
|
*
|
||||||
|
* @param[in] bus 1-Wire bus handle
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Reset 1-Wire bus successfully and find device on the bus
|
||||||
|
* - ESP_ERR_NOT_FOUND: Reset 1-Wire bus successfully but no device found on the bus
|
||||||
|
* - ESP_FAIL: Reset 1-Wire bus failed because of other errors
|
||||||
|
*/
|
||||||
|
esp_err_t onewire_bus_reset(onewire_bus_handle_t bus);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Free 1-Wire bus resources
|
||||||
|
*
|
||||||
|
* @param[in] bus 1-Wire bus handle
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Free resources successfully
|
||||||
|
* - ESP_FAIL: Free resources failed because error occurred
|
||||||
|
*/
|
||||||
|
esp_err_t onewire_bus_del(onewire_bus_handle_t bus);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "onewire_types.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 1-Wire bus RMT specific configuration
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint32_t max_rx_bytes; /*!< Set the largest possible single receive size,
|
||||||
|
which determines the size of the internal buffer that used to save the receiving RMT symbols */
|
||||||
|
} onewire_bus_rmt_config_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create 1-Wire bus with RMT backend
|
||||||
|
*
|
||||||
|
* @note One 1-Wire bus utilizes a pair of RMT TX and RX channels
|
||||||
|
*
|
||||||
|
* @param[in] bus_config 1-Wire bus configuration
|
||||||
|
* @param[in] rmt_config RMT specific configuration
|
||||||
|
* @param[out] ret_bus Returned 1-Wire bus handle
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: create 1-Wire bus handle successfully
|
||||||
|
* - ESP_ERR_INVALID_ARG: create 1-Wire bus handle failed because of invalid argument
|
||||||
|
* - ESP_ERR_NO_MEM: create 1-Wire bus handle failed because of out of memory
|
||||||
|
* - ESP_FAIL: create 1-Wire bus handle failed because some other error
|
||||||
|
*/
|
||||||
|
esp_err_t onewire_new_bus_rmt(const onewire_bus_config_t *bus_config, const onewire_bus_rmt_config_t *rmt_config, onewire_bus_handle_t *ret_bus);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "onewire_types.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 1-Wire bus UART specific configuration
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
int uart_port_num; /*!< UART port number, e.g. UART_NUM_1 */
|
||||||
|
} onewire_bus_uart_config_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create 1-Wire bus with UART backend
|
||||||
|
*
|
||||||
|
* @note TX and RX will both be configured to bus_config->bus_gpio_num.
|
||||||
|
* And this GPIO will be configured as open-drain mode.
|
||||||
|
*
|
||||||
|
* @param[in] bus_config 1-Wire bus configuration
|
||||||
|
* @param[in] uart_config UART specific configuration
|
||||||
|
* @param[out] ret_bus Returned 1-Wire bus handle
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: create 1-Wire bus handle successfully
|
||||||
|
* - ESP_ERR_INVALID_ARG: create 1-Wire bus handle failed because of invalid argument
|
||||||
|
* - ESP_ERR_NO_MEM: create 1-Wire bus handle failed because of out of memory
|
||||||
|
* - ESP_FAIL: create 1-Wire bus handle failed because some other error
|
||||||
|
*/
|
||||||
|
esp_err_t onewire_new_bus_uart(const onewire_bus_config_t *bus_config, const onewire_bus_uart_config_t *uart_config, onewire_bus_handle_t *ret_bus);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define ONEWIRE_CMD_SEARCH_NORMAL 0xF0
|
||||||
|
#define ONEWIRE_CMD_MATCH_ROM 0x55
|
||||||
|
#define ONEWIRE_CMD_SKIP_ROM 0xCC
|
||||||
|
#define ONEWIRE_CMD_SEARCH_ALARM 0xEC
|
||||||
|
#define ONEWIRE_CMD_READ_POWER_SUPPLY 0xB4
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate Dallas CRC8 value of a given buffer
|
||||||
|
*
|
||||||
|
* @param[in] init_crc Initial CRC value
|
||||||
|
* @param[in] input Input buffer to calculate CRC value
|
||||||
|
* @param[in] input_size Size of input buffer, in bytes
|
||||||
|
* @return CRC8 result of the input buffer
|
||||||
|
*/
|
||||||
|
uint8_t onewire_crc8(uint8_t init_crc, uint8_t *input, size_t input_size);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "onewire_types.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 1-Wire device generic type
|
||||||
|
*/
|
||||||
|
typedef struct onewire_device_t {
|
||||||
|
onewire_bus_handle_t bus; /*!< Which bus the 1-Wire device is attached to */
|
||||||
|
onewire_device_address_t address; /*!< Device address (represented by its internal ROM ID) */
|
||||||
|
} onewire_device_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create an iterator to enumerate the 1-Wire devices on the bus
|
||||||
|
*
|
||||||
|
* @param[in] bus 1-Wire bus handle
|
||||||
|
* @param[out] ret_iter Returned created device iterator
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Create device iterator successfully
|
||||||
|
* - ESP_ERR_INVALID_ARG: Invalid argument
|
||||||
|
* - ESP_ERR_NO_MEM: No memory to create device iterator
|
||||||
|
* - ESP_FAIL: Other errors
|
||||||
|
*/
|
||||||
|
esp_err_t onewire_new_device_iter(onewire_bus_handle_t bus, onewire_device_iter_handle_t *ret_iter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delete the device iterator
|
||||||
|
*
|
||||||
|
* @param[in] iter Device iterator handle
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Delete device iterator successfully
|
||||||
|
* - ESP_ERR_INVALID_ARG: Invalid argument
|
||||||
|
* - ESP_FAIL: Other errors
|
||||||
|
*/
|
||||||
|
esp_err_t onewire_del_device_iter(onewire_device_iter_handle_t iter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the next 1-Wire device from the iterator
|
||||||
|
*
|
||||||
|
* @param[in] iter Device iterator handle
|
||||||
|
* @param[out] dev Returned 1-Wire device handle
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Get next device successfully
|
||||||
|
* - ESP_ERR_INVALID_ARG: Invalid argument
|
||||||
|
* - ESP_ERR_NOT_FOUND: No more device to get
|
||||||
|
* - ESP_FAIL: Other errors
|
||||||
|
*/
|
||||||
|
esp_err_t onewire_device_iter_get_next(onewire_device_iter_handle_t iter, onewire_device_t *dev);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Type of 1-Wire bus handle
|
||||||
|
*/
|
||||||
|
typedef struct onewire_bus_t *onewire_bus_handle_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Type of the address for a 1-Wire compatible device
|
||||||
|
*/
|
||||||
|
typedef uint64_t onewire_device_address_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Type of 1-Wire device iterator handle
|
||||||
|
*/
|
||||||
|
typedef struct onewire_device_iter_t *onewire_device_iter_handle_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 1-Wire bus configuration
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
int bus_gpio_num; /*!< GPIO number that used by the 1-Wire bus */
|
||||||
|
struct onewire_bus_config_flags {
|
||||||
|
uint32_t en_pull_up: 1; /*!< Set true to enable internal pull-up resistor.
|
||||||
|
Please note the internal pull-up resistor cannot provide enough current for some devices,
|
||||||
|
so external pull-up resistor is still recommended. */
|
||||||
|
} flags; /*!< Configuration flags for the bus */
|
||||||
|
} onewire_bus_config_t;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct onewire_bus_t onewire_bus_t; /*!< Type of 1-Wire bus */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 1-Wire bus interface definition
|
||||||
|
*/
|
||||||
|
struct onewire_bus_t {
|
||||||
|
/**
|
||||||
|
* @brief Write bytes to 1-wire bus
|
||||||
|
*
|
||||||
|
* @note This is a blocking function
|
||||||
|
*
|
||||||
|
* @param[in] bus 1-Wire bus handle
|
||||||
|
* @param[in] tx_data pointer to data to be sent
|
||||||
|
* @param[in] tx_data_size size of data to be sent, in bytes
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Write bytes to 1-Wire bus successfully
|
||||||
|
* - ESP_ERR_INVALID_ARG: Write bytes to 1-Wire bus failed because of invalid argument
|
||||||
|
* - ESP_FAIL: Write bytes to 1-Wire bus failed because of other errors
|
||||||
|
*/
|
||||||
|
esp_err_t (*write_bytes)(onewire_bus_t *bus, const uint8_t *tx_data, uint8_t tx_data_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read bytes from 1-wire bus
|
||||||
|
*
|
||||||
|
* @param[in] bus 1-wire bus handle
|
||||||
|
* @param[out] rx_buf pointer to buffer to store received data
|
||||||
|
* @param[in] rx_buf_size size of buffer to store received data, in bytes
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Read bytes from 1-Wire bus successfully
|
||||||
|
* - ESP_ERR_INVALID_ARG: Read bytes from 1-Wire bus failed because of invalid argument
|
||||||
|
* - ESP_FAIL: Read bytes from 1-Wire bus failed because of other errors
|
||||||
|
*/
|
||||||
|
esp_err_t (*read_bytes)(onewire_bus_t *bus, uint8_t *rx_buf, size_t rx_buf_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a bit to 1-wire bus, this is a blocking function
|
||||||
|
*
|
||||||
|
* @param[in] handle 1-wire bus handle
|
||||||
|
* @param[in] tx_bit bit to transmit, 0 for zero bit, other for one bit
|
||||||
|
* @return
|
||||||
|
* - ESP_OK Write bit to 1-wire bus successfully.
|
||||||
|
* - ESP_ERR_INVALID_ARG Invalid argument.
|
||||||
|
*/
|
||||||
|
esp_err_t (*write_bit)(onewire_bus_handle_t handle, uint8_t tx_bit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read a bit from 1-wire bus
|
||||||
|
*
|
||||||
|
* @param[in] handle 1-wire bus handle
|
||||||
|
* @param[out] rx_bit received bit, 0 for zero bit, 1 for one bit
|
||||||
|
* @return
|
||||||
|
* - ESP_OK Read bit from 1-wire bus successfully.
|
||||||
|
* - ESP_ERR_INVALID_ARG Invalid argument.
|
||||||
|
*/
|
||||||
|
esp_err_t (*read_bit)(onewire_bus_handle_t handle, uint8_t *rx_bit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send reset pulse to the bus, and check if there are devices attached to the bus
|
||||||
|
*
|
||||||
|
* @param[in] bus 1-Wire bus handle
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Reset 1-Wire bus successfully and find device on the bus
|
||||||
|
* - ESP_ERR_NOT_FOUND: Reset 1-Wire bus successfully but no device found on the bus
|
||||||
|
* - ESP_FAIL: Reset 1-Wire bus failed because of other errors
|
||||||
|
*/
|
||||||
|
esp_err_t (*reset)(onewire_bus_t *bus);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Free 1-Wire bus resources
|
||||||
|
*
|
||||||
|
* @param[in] bus 1-Wire bus handle
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Free resources successfully
|
||||||
|
* - ESP_FAIL: Free resources failed because error occurred
|
||||||
|
*/
|
||||||
|
esp_err_t (*del)(onewire_bus_t *bus);
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_check.h"
|
||||||
|
#include "onewire_types.h"
|
||||||
|
#include "onewire_bus_interface.h"
|
||||||
|
|
||||||
|
static const char *TAG = "1-wire";
|
||||||
|
|
||||||
|
esp_err_t onewire_bus_reset(onewire_bus_handle_t bus)
|
||||||
|
{
|
||||||
|
ESP_RETURN_ON_FALSE(bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
return bus->reset(bus);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t onewire_bus_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size)
|
||||||
|
{
|
||||||
|
ESP_RETURN_ON_FALSE(bus && tx_data && tx_data_size, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
return bus->write_bytes(bus, tx_data, tx_data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t onewire_bus_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size)
|
||||||
|
{
|
||||||
|
ESP_RETURN_ON_FALSE(bus && rx_buf && rx_buf_size, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
return bus->read_bytes(bus, rx_buf, rx_buf_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t onewire_bus_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit)
|
||||||
|
{
|
||||||
|
ESP_RETURN_ON_FALSE(bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
return bus->write_bit(bus, tx_bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t onewire_bus_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit)
|
||||||
|
{
|
||||||
|
ESP_RETURN_ON_FALSE(bus && rx_bit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
return bus->read_bit(bus, rx_bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t onewire_bus_del(onewire_bus_handle_t bus)
|
||||||
|
{
|
||||||
|
ESP_RETURN_ON_FALSE(bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
return bus->del(bus);
|
||||||
|
}
|
||||||
@@ -0,0 +1,521 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#include <string.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "esp_check.h"
|
||||||
|
#include "esp_attr.h"
|
||||||
|
#include "driver/rmt_tx.h"
|
||||||
|
#include "driver/rmt_rx.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "esp_private/gpio.h"
|
||||||
|
#include "onewire_bus_impl_rmt.h"
|
||||||
|
#include "onewire_bus_interface.h"
|
||||||
|
#include "esp_idf_version.h"
|
||||||
|
|
||||||
|
static const char *TAG = "1-wire.rmt";
|
||||||
|
|
||||||
|
#define ONEWIRE_RMT_RESOLUTION_HZ 1000000 // RMT channel default resolution for 1-wire bus, 1MHz, 1tick = 1us
|
||||||
|
#define ONEWIRE_RMT_DEFAULT_TRANS_QUEUE_SIZE 4
|
||||||
|
|
||||||
|
// the memory size of each RMT channel, in words (4 bytes)
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#define ONEWIRE_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 64
|
||||||
|
#else
|
||||||
|
#define ONEWIRE_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 48
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// for chips whose RMT RX channel doesn't support ping-pong, we need the user to tell the maximum number of bytes will be received
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
// one RMT symbol represents one bit, so x8
|
||||||
|
#define ONEWIRE_RMT_RX_MEM_BLOCK_SIZE (rmt_config->max_rx_bytes * 8)
|
||||||
|
#else // otherwise, we just use one memory block, to save resources
|
||||||
|
#define ONEWIRE_RMT_RX_MEM_BLOCK_SIZE ONEWIRE_RMT_DEFAULT_MEM_BLOCK_SYMBOLS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset Pulse:
|
||||||
|
|
||||||
|
| RESET_PULSE | RESET_WAIT_DURATION |
|
||||||
|
| _DURATION | |
|
||||||
|
| | | | RESET | |
|
||||||
|
| | * | | _PRESENCE | |
|
||||||
|
| | | | _DURATION | |
|
||||||
|
----------+ +-----+ +--------------
|
||||||
|
| | | |
|
||||||
|
| | | |
|
||||||
|
| | | |
|
||||||
|
+-------------+ +-----------+
|
||||||
|
*: RESET_PRESENCE_WAIT_DURATION
|
||||||
|
*/
|
||||||
|
#define ONEWIRE_RESET_PULSE_DURATION 500 // duration of reset bit
|
||||||
|
#define ONEWIRE_RESET_WAIT_DURATION 200 // how long should master wait for device to show its presence
|
||||||
|
#define ONEWIRE_RESET_PRESENCE_WAIT_DURATION_MIN 15 // minimum duration for master to wait device to show its presence
|
||||||
|
#define ONEWIRE_RESET_PRESENCE_DURATION_MIN 60 // minimum duration for master to recognize device as present
|
||||||
|
|
||||||
|
/*
|
||||||
|
Write 1 bit:
|
||||||
|
|
||||||
|
| SLOT_START | SLOT_BIT | SLOT_RECOVERY | NEXT
|
||||||
|
| _DURATION | _DURATION | _DURATION | SLOT
|
||||||
|
| | | |
|
||||||
|
----------+ +-------------------------------------
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
+------------+
|
||||||
|
|
||||||
|
Write 0 bit:
|
||||||
|
|
||||||
|
| SLOT_START | SLOT_BIT | SLOT_RECOVERY | NEXT
|
||||||
|
| _DURATION | _DURATION | _DURATION | SLOT
|
||||||
|
| | | |
|
||||||
|
----------+ +-------------------------
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
+------------------------+
|
||||||
|
|
||||||
|
Read 1 bit:
|
||||||
|
|
||||||
|
|
||||||
|
| SLOT_START | SLOT_BIT_DURATION | SLOT_RECOVERY | NEXT
|
||||||
|
| _DURATION | | _DURATION | SLOT
|
||||||
|
| | SLOT_BIT_ | | |
|
||||||
|
| | SAMPLE_TIME | | |
|
||||||
|
----------+ +----------------------------------------------
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
+------------+
|
||||||
|
|
||||||
|
Read 0 bit:
|
||||||
|
|
||||||
|
| SLOT_START | SLOT_BIT_DURATION | SLOT_RECOVERY | NEXT
|
||||||
|
| _DURATION | | _DURATION | SLOT
|
||||||
|
| | SLOT_BIT_ | | |
|
||||||
|
| | SAMPLE_TIME | | |
|
||||||
|
----------+ | | +-----------------------------
|
||||||
|
| | |
|
||||||
|
| | PULLED DOWN |
|
||||||
|
| | BY DEVICE |
|
||||||
|
+-----------------------------+
|
||||||
|
*/
|
||||||
|
#define ONEWIRE_SLOT_START_DURATION 2 // bit start pulse duration
|
||||||
|
#define ONEWIRE_SLOT_BIT_DURATION 60 // duration for each bit to transmit
|
||||||
|
// refer to https://www.maximintegrated.com/en/design/technical-documents/app-notes/3/3829.html for more information
|
||||||
|
#define ONEWIRE_SLOT_RECOVERY_DURATION 5 // recovery time between each bit, should be longer in parasite power mode
|
||||||
|
#define ONEWIRE_SLOT_BIT_SAMPLE_TIME 15 // how long after bit start pulse should the master sample from the bus
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
onewire_bus_t base; /*!< base class */
|
||||||
|
rmt_channel_handle_t tx_channel; /*!< rmt tx channel handler */
|
||||||
|
rmt_channel_handle_t rx_channel; /*!< rmt rx channel handler */
|
||||||
|
|
||||||
|
gpio_num_t data_gpio_num; /*!< GPIO number for 1-wire bus */
|
||||||
|
|
||||||
|
rmt_encoder_handle_t tx_bytes_encoder; /*!< used to encode commands and data */
|
||||||
|
rmt_encoder_handle_t tx_copy_encoder; /*!< used to encode reset pulse and bits */
|
||||||
|
|
||||||
|
rmt_symbol_word_t *rx_symbols_buf; /*!< hold rmt raw symbols */
|
||||||
|
|
||||||
|
size_t max_rx_bytes; /*!< buffer size in byte for single receive transaction */
|
||||||
|
|
||||||
|
QueueHandle_t receive_queue;
|
||||||
|
SemaphoreHandle_t bus_mutex;
|
||||||
|
} onewire_bus_rmt_obj_t;
|
||||||
|
|
||||||
|
static rmt_symbol_word_t onewire_reset_pulse_symbol = {
|
||||||
|
.level0 = 0,
|
||||||
|
.duration0 = ONEWIRE_RESET_PULSE_DURATION,
|
||||||
|
.level1 = 1,
|
||||||
|
.duration1 = ONEWIRE_RESET_WAIT_DURATION
|
||||||
|
};
|
||||||
|
|
||||||
|
static rmt_symbol_word_t onewire_bit0_symbol = {
|
||||||
|
.level0 = 0,
|
||||||
|
.duration0 = ONEWIRE_SLOT_START_DURATION + ONEWIRE_SLOT_BIT_DURATION,
|
||||||
|
.level1 = 1,
|
||||||
|
.duration1 = ONEWIRE_SLOT_RECOVERY_DURATION
|
||||||
|
};
|
||||||
|
|
||||||
|
static rmt_symbol_word_t onewire_bit1_symbol = {
|
||||||
|
.level0 = 0,
|
||||||
|
.duration0 = ONEWIRE_SLOT_START_DURATION,
|
||||||
|
.level1 = 1,
|
||||||
|
.duration1 = ONEWIRE_SLOT_BIT_DURATION + ONEWIRE_SLOT_RECOVERY_DURATION
|
||||||
|
};
|
||||||
|
|
||||||
|
const static rmt_transmit_config_t onewire_rmt_tx_config = {
|
||||||
|
.loop_count = 0, // no transfer loop
|
||||||
|
.flags.eot_level = 1 // onewire bus should be released in IDLE
|
||||||
|
};
|
||||||
|
|
||||||
|
const static rmt_receive_config_t onewire_rmt_rx_config = {
|
||||||
|
.signal_range_min_ns = 1000000000 / ONEWIRE_RMT_RESOLUTION_HZ,
|
||||||
|
.signal_range_max_ns = (ONEWIRE_RESET_PULSE_DURATION + ONEWIRE_RESET_WAIT_DURATION) * 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_rmt_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit);
|
||||||
|
static esp_err_t onewire_bus_rmt_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit);
|
||||||
|
static esp_err_t onewire_bus_rmt_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size);
|
||||||
|
static esp_err_t onewire_bus_rmt_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size);
|
||||||
|
static esp_err_t onewire_bus_rmt_reset(onewire_bus_handle_t bus);
|
||||||
|
static esp_err_t onewire_bus_rmt_del(onewire_bus_handle_t bus);
|
||||||
|
static esp_err_t onewire_bus_rmt_destroy(onewire_bus_rmt_obj_t *bus_rmt);
|
||||||
|
|
||||||
|
IRAM_ATTR
|
||||||
|
bool onewire_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
|
||||||
|
{
|
||||||
|
BaseType_t task_woken = pdFALSE;
|
||||||
|
onewire_bus_rmt_obj_t *bus_rmt = (onewire_bus_rmt_obj_t *)user_data;
|
||||||
|
|
||||||
|
xQueueSendFromISR(bus_rmt->receive_queue, edata, &task_woken);
|
||||||
|
|
||||||
|
return task_woken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
[0].0 means symbol[0].duration0
|
||||||
|
|
||||||
|
First reset pulse after rmt channel init:
|
||||||
|
|
||||||
|
Bus is low | Reset | Wait | Device | Bus Idle
|
||||||
|
after init | Pulse | | Presence |
|
||||||
|
+------+ +-----------
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
-------------------+ +----------+
|
||||||
|
1 2 3
|
||||||
|
|
||||||
|
[0].1 [0].0 [1].1 [1].0
|
||||||
|
|
||||||
|
|
||||||
|
Following reset pulses:
|
||||||
|
|
||||||
|
Bus is high | Reset | Wait | Device | Bus Idle
|
||||||
|
after init | Pulse | | Presence |
|
||||||
|
------------+ +------+ +-----------
|
||||||
|
| | | |
|
||||||
|
| | | |
|
||||||
|
| | | |
|
||||||
|
+-------+ +----------+
|
||||||
|
1 2 3 4
|
||||||
|
|
||||||
|
[0].0 [0].1 [1].0 [1].1
|
||||||
|
*/
|
||||||
|
static bool onewire_rmt_check_presence_pulse(rmt_symbol_word_t *rmt_symbols, size_t symbol_num)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
if (symbol_num >= 2) { // there should be at lease 2 symbols(3 or 4 edges)
|
||||||
|
if (rmt_symbols[0].level1 == 1) { // bus is high before reset pulse
|
||||||
|
if (rmt_symbols[0].duration1 > ONEWIRE_RESET_PRESENCE_WAIT_DURATION_MIN &&
|
||||||
|
rmt_symbols[1].duration0 > ONEWIRE_RESET_PRESENCE_DURATION_MIN) {
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
} else { // bus is low before reset pulse(first pulse after rmt channel init)
|
||||||
|
if (rmt_symbols[0].duration0 > ONEWIRE_RESET_PRESENCE_WAIT_DURATION_MIN &&
|
||||||
|
rmt_symbols[1].duration1 > ONEWIRE_RESET_PRESENCE_DURATION_MIN) {
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onewire_rmt_decode_data(rmt_symbol_word_t *rmt_symbols, size_t symbol_num, uint8_t *rx_buf, size_t rx_buf_size)
|
||||||
|
{
|
||||||
|
size_t byte_pos = 0;
|
||||||
|
size_t bit_pos = 0;
|
||||||
|
for (size_t i = 0; i < symbol_num; i ++) {
|
||||||
|
if (rmt_symbols[i].duration0 > ONEWIRE_SLOT_BIT_SAMPLE_TIME) { // 0 bit
|
||||||
|
rx_buf[byte_pos] &= ~(1 << bit_pos); // LSB first
|
||||||
|
} else { // 1 bit
|
||||||
|
rx_buf[byte_pos] |= 1 << bit_pos;
|
||||||
|
}
|
||||||
|
bit_pos ++;
|
||||||
|
if (bit_pos >= 8) {
|
||||||
|
bit_pos = 0;
|
||||||
|
byte_pos ++;
|
||||||
|
if (byte_pos >= rx_buf_size) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t onewire_new_bus_rmt(const onewire_bus_config_t *bus_config, const onewire_bus_rmt_config_t *rmt_config, onewire_bus_handle_t *ret_bus)
|
||||||
|
{
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
onewire_bus_rmt_obj_t *bus_rmt = NULL;
|
||||||
|
ESP_RETURN_ON_FALSE(bus_config && rmt_config && ret_bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
|
||||||
|
bus_rmt = calloc(1, sizeof(onewire_bus_rmt_obj_t));
|
||||||
|
ESP_RETURN_ON_FALSE(bus_rmt, ESP_ERR_NO_MEM, TAG, "no mem for onewire_bus_rmt_obj_t");
|
||||||
|
bus_rmt->data_gpio_num = GPIO_NUM_NC;
|
||||||
|
|
||||||
|
// create rmt bytes encoder to transmit 1-wire commands and data
|
||||||
|
rmt_bytes_encoder_config_t bytes_encoder_config = {
|
||||||
|
.bit0 = onewire_bit0_symbol,
|
||||||
|
.bit1 = onewire_bit1_symbol,
|
||||||
|
.flags.msb_first = 0,
|
||||||
|
};
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &bus_rmt->tx_bytes_encoder),
|
||||||
|
err, TAG, "create bytes encoder failed");
|
||||||
|
|
||||||
|
// create rmt copy encoder to transmit 1-wire reset pulse or bits
|
||||||
|
rmt_copy_encoder_config_t copy_encoder_config = {};
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &bus_rmt->tx_copy_encoder),
|
||||||
|
err, TAG, "create copy encoder failed");
|
||||||
|
|
||||||
|
// create RX and TX channels and bind them to the same GPIO
|
||||||
|
rmt_rx_channel_config_t onewire_rx_channel_cfg = {
|
||||||
|
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||||
|
.resolution_hz = ONEWIRE_RMT_RESOLUTION_HZ,
|
||||||
|
.gpio_num = bus_config->bus_gpio_num,
|
||||||
|
.mem_block_symbols = ONEWIRE_RMT_RX_MEM_BLOCK_SIZE,
|
||||||
|
};
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_new_rx_channel(&onewire_rx_channel_cfg, &bus_rmt->rx_channel),
|
||||||
|
err, TAG, "create rmt rx channel failed");
|
||||||
|
|
||||||
|
rmt_tx_channel_config_t onewire_tx_channel_cfg = {
|
||||||
|
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||||
|
.resolution_hz = ONEWIRE_RMT_RESOLUTION_HZ,
|
||||||
|
.gpio_num = bus_config->bus_gpio_num,
|
||||||
|
.mem_block_symbols = ONEWIRE_RMT_DEFAULT_MEM_BLOCK_SYMBOLS,
|
||||||
|
.trans_queue_depth = ONEWIRE_RMT_DEFAULT_TRANS_QUEUE_SIZE,
|
||||||
|
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||||
|
.flags.io_loop_back = true,
|
||||||
|
.flags.io_od_mode = true,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&onewire_tx_channel_cfg, &bus_rmt->tx_channel),
|
||||||
|
err, TAG, "create rmt tx channel failed");
|
||||||
|
|
||||||
|
bus_rmt->data_gpio_num = bus_config->bus_gpio_num;
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||||
|
// enable open-drain mode for 1-wire bus
|
||||||
|
gpio_od_enable(bus_rmt->data_gpio_num);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (bus_config->flags.en_pull_up) {
|
||||||
|
// enable internal pull-up resistor and disable pull-down resistor
|
||||||
|
gpio_set_pull_mode(bus_rmt->data_gpio_num, GPIO_PULLUP_ONLY);
|
||||||
|
} else {
|
||||||
|
// disable internal pull-up and pull-down resistors
|
||||||
|
gpio_set_pull_mode(bus_rmt->data_gpio_num, GPIO_FLOATING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate rmt rx symbol buffer, one RMT symbol represents one bit, so x8
|
||||||
|
bus_rmt->rx_symbols_buf = malloc(rmt_config->max_rx_bytes * sizeof(rmt_symbol_word_t) * 8);
|
||||||
|
ESP_GOTO_ON_FALSE(bus_rmt->rx_symbols_buf, ESP_ERR_NO_MEM, err, TAG, "no mem to store received RMT symbols");
|
||||||
|
bus_rmt->max_rx_bytes = rmt_config->max_rx_bytes;
|
||||||
|
|
||||||
|
bus_rmt->receive_queue = xQueueCreate(1, sizeof(rmt_rx_done_event_data_t));
|
||||||
|
ESP_GOTO_ON_FALSE(bus_rmt->receive_queue, ESP_ERR_NO_MEM, err, TAG, "receive queue creation failed");
|
||||||
|
|
||||||
|
bus_rmt->bus_mutex = xSemaphoreCreateMutex();
|
||||||
|
ESP_GOTO_ON_FALSE(bus_rmt->bus_mutex, ESP_ERR_NO_MEM, err, TAG, "bus mutex creation failed");
|
||||||
|
|
||||||
|
// register rmt rx done callback
|
||||||
|
rmt_rx_event_callbacks_t cbs = {
|
||||||
|
.on_recv_done = onewire_rmt_rx_done_callback
|
||||||
|
};
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_rx_register_event_callbacks(bus_rmt->rx_channel, &cbs, bus_rmt),
|
||||||
|
err, TAG, "enable rmt rx channel failed");
|
||||||
|
|
||||||
|
// enable rmt channels
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_enable(bus_rmt->rx_channel), err, TAG, "enable rmt rx channel failed");
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_enable(bus_rmt->tx_channel), err, TAG, "enable rmt tx channel failed");
|
||||||
|
|
||||||
|
// release the bus by sending a special RMT symbol
|
||||||
|
static rmt_symbol_word_t release_symbol = {
|
||||||
|
.level0 = 1,
|
||||||
|
.duration0 = 1,
|
||||||
|
.level1 = 1,
|
||||||
|
.duration1 = 0,
|
||||||
|
};
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_copy_encoder, &release_symbol,
|
||||||
|
sizeof(release_symbol), &onewire_rmt_tx_config), err, TAG, "release bus failed");
|
||||||
|
|
||||||
|
bus_rmt->base.del = onewire_bus_rmt_del;
|
||||||
|
bus_rmt->base.reset = onewire_bus_rmt_reset;
|
||||||
|
bus_rmt->base.write_bit = onewire_bus_rmt_write_bit;
|
||||||
|
bus_rmt->base.write_bytes = onewire_bus_rmt_write_bytes;
|
||||||
|
bus_rmt->base.read_bit = onewire_bus_rmt_read_bit;
|
||||||
|
bus_rmt->base.read_bytes = onewire_bus_rmt_read_bytes;
|
||||||
|
*ret_bus = &bus_rmt->base;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
err:
|
||||||
|
if (bus_rmt) {
|
||||||
|
onewire_bus_rmt_destroy(bus_rmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_rmt_destroy(onewire_bus_rmt_obj_t *bus_rmt)
|
||||||
|
{
|
||||||
|
if (bus_rmt->tx_bytes_encoder) {
|
||||||
|
rmt_del_encoder(bus_rmt->tx_bytes_encoder);
|
||||||
|
}
|
||||||
|
if (bus_rmt->tx_copy_encoder) {
|
||||||
|
rmt_del_encoder(bus_rmt->tx_copy_encoder);
|
||||||
|
}
|
||||||
|
if (bus_rmt->rx_channel) {
|
||||||
|
rmt_disable(bus_rmt->rx_channel);
|
||||||
|
rmt_del_channel(bus_rmt->rx_channel);
|
||||||
|
}
|
||||||
|
if (bus_rmt->tx_channel) {
|
||||||
|
rmt_disable(bus_rmt->tx_channel);
|
||||||
|
rmt_del_channel(bus_rmt->tx_channel);
|
||||||
|
}
|
||||||
|
if (bus_rmt->receive_queue) {
|
||||||
|
vQueueDelete(bus_rmt->receive_queue);
|
||||||
|
}
|
||||||
|
if (bus_rmt->bus_mutex) {
|
||||||
|
vSemaphoreDelete(bus_rmt->bus_mutex);
|
||||||
|
}
|
||||||
|
if (bus_rmt->rx_symbols_buf) {
|
||||||
|
free(bus_rmt->rx_symbols_buf);
|
||||||
|
}
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||||
|
if (bus_rmt->data_gpio_num != GPIO_NUM_NC) {
|
||||||
|
gpio_od_disable(bus_rmt->data_gpio_num);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
free(bus_rmt);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_rmt_del(onewire_bus_handle_t bus)
|
||||||
|
{
|
||||||
|
onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base);
|
||||||
|
return onewire_bus_rmt_destroy(bus_rmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_rmt_reset(onewire_bus_handle_t bus)
|
||||||
|
{
|
||||||
|
onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base);
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY);
|
||||||
|
// send reset pulse while receive presence pulse
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_receive(bus_rmt->rx_channel, bus_rmt->rx_symbols_buf, sizeof(rmt_symbol_word_t) * 2, &onewire_rmt_rx_config),
|
||||||
|
err, TAG, "1-wire reset pulse receive failed");
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_copy_encoder, &onewire_reset_pulse_symbol, sizeof(onewire_reset_pulse_symbol), &onewire_rmt_tx_config),
|
||||||
|
err, TAG, "1-wire reset pulse transmit failed");
|
||||||
|
|
||||||
|
// wait and check presence pulse
|
||||||
|
rmt_rx_done_event_data_t rmt_rx_evt_data;
|
||||||
|
ESP_GOTO_ON_FALSE(xQueueReceive(bus_rmt->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS,
|
||||||
|
ESP_ERR_TIMEOUT, err, TAG, "1-wire reset pulse receive timeout");
|
||||||
|
if (onewire_rmt_check_presence_pulse(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols) == false) {
|
||||||
|
ret = ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
err:
|
||||||
|
xSemaphoreGive(bus_rmt->bus_mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_rmt_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size)
|
||||||
|
{
|
||||||
|
onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base);
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY);
|
||||||
|
// transmit data with the bytes encoder
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_bytes_encoder, tx_data, tx_data_size, &onewire_rmt_tx_config),
|
||||||
|
err, TAG, "1-wire data transmit failed");
|
||||||
|
// wait the transmission to complete
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_tx_wait_all_done(bus_rmt->tx_channel, 50), err, TAG, "wait for 1-wire data transmit failed");
|
||||||
|
|
||||||
|
err:
|
||||||
|
xSemaphoreGive(bus_rmt->bus_mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// While receiving data, we use rmt transmit channel to send 0xFF to generate read pulse,
|
||||||
|
// at the same time, receive channel is used to record weather the bus is pulled down by device.
|
||||||
|
static esp_err_t onewire_bus_rmt_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size)
|
||||||
|
{
|
||||||
|
onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base);
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
ESP_RETURN_ON_FALSE(rx_buf_size <= bus_rmt->max_rx_bytes, ESP_ERR_INVALID_ARG, TAG, "rx_buf_size too large for buffer to hold");
|
||||||
|
memset(rx_buf, 0, rx_buf_size);
|
||||||
|
|
||||||
|
xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY);
|
||||||
|
|
||||||
|
// transmit one bits to generate read clock
|
||||||
|
uint8_t tx_buffer[rx_buf_size];
|
||||||
|
memset(tx_buffer, 0xFF, rx_buf_size);
|
||||||
|
// transmit 1 bits while receiving
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_receive(bus_rmt->rx_channel, bus_rmt->rx_symbols_buf, rx_buf_size * 8 * sizeof(rmt_symbol_word_t), &onewire_rmt_rx_config),
|
||||||
|
err, TAG, "1-wire data receive failed");
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_bytes_encoder, tx_buffer, sizeof(tx_buffer), &onewire_rmt_tx_config),
|
||||||
|
err, TAG, "1-wire data transmit failed");
|
||||||
|
|
||||||
|
// wait the transmission finishes and decode data
|
||||||
|
rmt_rx_done_event_data_t rmt_rx_evt_data;
|
||||||
|
ESP_GOTO_ON_FALSE(xQueueReceive(bus_rmt->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS, ESP_ERR_TIMEOUT,
|
||||||
|
err, TAG, "1-wire data receive timeout");
|
||||||
|
onewire_rmt_decode_data(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols, rx_buf, rx_buf_size);
|
||||||
|
|
||||||
|
err:
|
||||||
|
xSemaphoreGive(bus_rmt->bus_mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_rmt_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit)
|
||||||
|
{
|
||||||
|
onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base);
|
||||||
|
const rmt_symbol_word_t *symbol_to_transmit = tx_bit ? &onewire_bit1_symbol : &onewire_bit0_symbol;
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY);
|
||||||
|
|
||||||
|
// transmit bit
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_copy_encoder, symbol_to_transmit, sizeof(rmt_symbol_word_t), &onewire_rmt_tx_config),
|
||||||
|
err, TAG, "1-wire bit transmit failed");
|
||||||
|
// wait the transmission to complete
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_tx_wait_all_done(bus_rmt->tx_channel, 50), err, TAG, "wait for 1-wire bit transmit failed");
|
||||||
|
|
||||||
|
err:
|
||||||
|
xSemaphoreGive(bus_rmt->bus_mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_rmt_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit)
|
||||||
|
{
|
||||||
|
onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base);
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY);
|
||||||
|
|
||||||
|
// transmit 1 bit while receiving
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_receive(bus_rmt->rx_channel, bus_rmt->rx_symbols_buf, sizeof(rmt_symbol_word_t), &onewire_rmt_rx_config),
|
||||||
|
err, TAG, "1-wire bit receive failed");
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_copy_encoder, &onewire_bit1_symbol, sizeof(rmt_symbol_word_t), &onewire_rmt_tx_config),
|
||||||
|
err, TAG, "1-wire bit transmit failed");
|
||||||
|
|
||||||
|
// wait the transmission finishes and decode data
|
||||||
|
rmt_rx_done_event_data_t rmt_rx_evt_data;
|
||||||
|
ESP_GOTO_ON_FALSE(xQueueReceive(bus_rmt->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS, ESP_ERR_TIMEOUT,
|
||||||
|
err, TAG, "1-wire bit receive timeout");
|
||||||
|
uint8_t rx_buffer = 0;
|
||||||
|
onewire_rmt_decode_data(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols, &rx_buffer, sizeof(rx_buffer));
|
||||||
|
*rx_bit = rx_buffer & 0x01;
|
||||||
|
|
||||||
|
err:
|
||||||
|
xSemaphoreGive(bus_rmt->bus_mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_check.h"
|
||||||
|
#include "driver/uart.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "onewire_bus_impl_uart.h"
|
||||||
|
#include "onewire_bus_interface.h"
|
||||||
|
#include "esp_idf_version.h"
|
||||||
|
|
||||||
|
static const char *TAG = "1-wire.uart";
|
||||||
|
|
||||||
|
#define ONEWIRE_UART_DEFAULT_TIMEOUT_MS 100
|
||||||
|
// refer to https://www.analog.com/en/resources/technical-articles/using-a-uart-to-implement-a-1wire-bus-master.html for more information
|
||||||
|
#define ONEWIRE_UART_BAUD_RESET 9600 // baud rate for reset pulse and presence detect
|
||||||
|
#define ONEWIRE_UART_BAUD_SLOT 115200 // baud rate for read and write operations
|
||||||
|
|
||||||
|
#define ONEWIRE_UART_RESET_TX 0xF0 // TX value for reset pulse and presence detect
|
||||||
|
#define ONEWIRE_UART_RESET_RX_NO_DEVICE 0xF0 // RX value when no device is present
|
||||||
|
|
||||||
|
#define ONEWIRE_UART_SLOT_TX_WRITE_1 0xFF // TX value for write 1
|
||||||
|
#define ONEWIRE_UART_SLOT_TX_WRITE_0 0x00 // TX value for write 0
|
||||||
|
#define ONEWIRE_UART_SLOT_TX_READ 0xFF // TX value for read
|
||||||
|
#define ONEWIRE_UART_SLOT_RX_READ_1 0xFF // RX value when read 1
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
onewire_bus_t base; /*!< base class */
|
||||||
|
uart_port_t uart_port_num; /*!< UART port number */
|
||||||
|
gpio_num_t data_gpio_num; /*!< GPIO number for 1-wire bus */
|
||||||
|
uint32_t current_baud_rate; /*!< Note: the baud rate returned by uart_get_baudrate() could have a slight deviation from the user-configured baud rate.
|
||||||
|
That's why we store the configured baud rate here. */
|
||||||
|
SemaphoreHandle_t bus_mutex;
|
||||||
|
} onewire_bus_uart_obj_t;
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_uart_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit);
|
||||||
|
static esp_err_t onewire_bus_uart_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit);
|
||||||
|
static esp_err_t onewire_bus_uart_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size);
|
||||||
|
static esp_err_t onewire_bus_uart_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size);
|
||||||
|
static esp_err_t onewire_bus_uart_reset(onewire_bus_handle_t bus);
|
||||||
|
static esp_err_t onewire_bus_uart_del(onewire_bus_handle_t bus);
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_uart_destroy(onewire_bus_uart_obj_t *bus_uart);
|
||||||
|
static esp_err_t onewire_bus_uart_set_baud_rate(onewire_bus_uart_obj_t *bus_uart, uint32_t baud_rate);
|
||||||
|
static esp_err_t onewire_bus_uart_exchange_byte(onewire_bus_uart_obj_t *bus_uart, uint8_t tx_data, uint8_t *rx_data);
|
||||||
|
static esp_err_t onewire_bus_uart_write_bit_nolock(onewire_bus_uart_obj_t *bus_uart, uint8_t tx_bit);
|
||||||
|
static esp_err_t onewire_bus_uart_read_bit_nolock(onewire_bus_uart_obj_t *bus_uart, uint8_t *rx_bit);
|
||||||
|
|
||||||
|
esp_err_t onewire_new_bus_uart(const onewire_bus_config_t *bus_config, const onewire_bus_uart_config_t *uart_config, onewire_bus_handle_t *ret_bus)
|
||||||
|
{
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
onewire_bus_uart_obj_t *bus_uart = NULL;
|
||||||
|
ESP_RETURN_ON_FALSE(bus_config && uart_config && ret_bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(bus_config->bus_gpio_num), ESP_ERR_INVALID_ARG, TAG, "invalid GPIO number");
|
||||||
|
|
||||||
|
bus_uart = calloc(1, sizeof(onewire_bus_uart_obj_t));
|
||||||
|
ESP_RETURN_ON_FALSE(bus_uart, ESP_ERR_NO_MEM, TAG, "no mem for onewire_bus_uart_obj_t");
|
||||||
|
bus_uart->uart_port_num = UART_NUM_MAX;
|
||||||
|
bus_uart->data_gpio_num = GPIO_NUM_NC;
|
||||||
|
|
||||||
|
const gpio_config_t io_conf = {
|
||||||
|
.pin_bit_mask = (1ULL << bus_config->bus_gpio_num),
|
||||||
|
.mode = GPIO_MODE_INPUT_OUTPUT_OD,
|
||||||
|
.pull_up_en = bus_config->flags.en_pull_up ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_DISABLE,
|
||||||
|
};
|
||||||
|
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "gpio config failed");
|
||||||
|
bus_uart->data_gpio_num = bus_config->bus_gpio_num;
|
||||||
|
|
||||||
|
// Simulate a 1-Wire bus using UART 8N1 mode
|
||||||
|
// refer to https://www.analog.com/en/resources/technical-articles/using-a-uart-to-implement-a-1wire-bus-master.html for more information
|
||||||
|
const uart_config_t uart_cfg = {
|
||||||
|
.baud_rate = ONEWIRE_UART_BAUD_RESET,
|
||||||
|
.data_bits = UART_DATA_8_BITS,
|
||||||
|
.parity = UART_PARITY_DISABLE,
|
||||||
|
.stop_bits = UART_STOP_BITS_1,
|
||||||
|
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||||
|
.source_clk = UART_SCLK_DEFAULT,
|
||||||
|
};
|
||||||
|
ESP_GOTO_ON_ERROR(uart_param_config(uart_config->uart_port_num, &uart_cfg), err, TAG, "uart param config failed");
|
||||||
|
bus_uart->current_baud_rate = uart_cfg.baud_rate;
|
||||||
|
ESP_GOTO_ON_ERROR(uart_set_pin(uart_config->uart_port_num, bus_config->bus_gpio_num, bus_config->bus_gpio_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE),
|
||||||
|
err, TAG, "uart set pin failed");
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
|
||||||
|
// Set the RX buffer to minimum size because we only need to receive 1 byte at a time
|
||||||
|
// Disable the TX buffer because we only need to send 1 byte at a time
|
||||||
|
ESP_GOTO_ON_ERROR(uart_driver_install(uart_config->uart_port_num, UART_HW_FIFO_LEN(uart_config->uart_port_num) + 1, 0, 0, NULL, 0),
|
||||||
|
err, TAG, "uart driver install failed");
|
||||||
|
#else
|
||||||
|
ESP_GOTO_ON_ERROR(uart_driver_install(uart_config->uart_port_num, UART_FIFO_LEN + 1, 0, 0, NULL, 0),
|
||||||
|
err, TAG, "uart driver install failed");
|
||||||
|
#endif
|
||||||
|
bus_uart->uart_port_num = uart_config->uart_port_num;
|
||||||
|
|
||||||
|
// Configuration optimized for this scenario
|
||||||
|
// We only need to receive 1 byte at a time, so set the rx full threshold to 1 for faster response
|
||||||
|
// Normally, the RX timeout interrupt is not expected to trigger. Setting the timeout to 1 here is simply to ensure a fast response
|
||||||
|
ESP_GOTO_ON_ERROR(uart_set_rx_full_threshold(uart_config->uart_port_num, 1), err, TAG, "uart set rx full threshold failed");
|
||||||
|
ESP_GOTO_ON_ERROR(uart_set_rx_timeout(uart_config->uart_port_num, 1), err, TAG, "uart set rx timeout failed");
|
||||||
|
|
||||||
|
bus_uart->bus_mutex = xSemaphoreCreateMutex();
|
||||||
|
ESP_GOTO_ON_FALSE(bus_uart->bus_mutex, ESP_ERR_NO_MEM, err, TAG, "bus mutex creation failed");
|
||||||
|
|
||||||
|
bus_uart->base.del = onewire_bus_uart_del;
|
||||||
|
bus_uart->base.reset = onewire_bus_uart_reset;
|
||||||
|
bus_uart->base.write_bit = onewire_bus_uart_write_bit;
|
||||||
|
bus_uart->base.write_bytes = onewire_bus_uart_write_bytes;
|
||||||
|
bus_uart->base.read_bit = onewire_bus_uart_read_bit;
|
||||||
|
bus_uart->base.read_bytes = onewire_bus_uart_read_bytes;
|
||||||
|
*ret_bus = &bus_uart->base;
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
err:
|
||||||
|
if (bus_uart) {
|
||||||
|
onewire_bus_uart_destroy(bus_uart);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_uart_destroy(onewire_bus_uart_obj_t *bus_uart)
|
||||||
|
{
|
||||||
|
if (bus_uart->bus_mutex) {
|
||||||
|
vSemaphoreDelete(bus_uart->bus_mutex);
|
||||||
|
}
|
||||||
|
if (bus_uart->uart_port_num != UART_NUM_MAX) {
|
||||||
|
uart_driver_delete(bus_uart->uart_port_num);
|
||||||
|
}
|
||||||
|
if (bus_uart->data_gpio_num != GPIO_NUM_NC) {
|
||||||
|
gpio_reset_pin(bus_uart->data_gpio_num);
|
||||||
|
}
|
||||||
|
free(bus_uart);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_uart_set_baud_rate(onewire_bus_uart_obj_t *bus_uart, uint32_t baud_rate)
|
||||||
|
{
|
||||||
|
if (bus_uart->current_baud_rate == baud_rate) {
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
esp_err_t ret = uart_set_baudrate(bus_uart->uart_port_num, baud_rate);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
bus_uart->current_baud_rate = baud_rate;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send and receive one byte over the UART bus.
|
||||||
|
*
|
||||||
|
* @note This function is used for:
|
||||||
|
* - reset pulse and presence detect
|
||||||
|
* - write or read one bit on the 1-wire bus
|
||||||
|
*/
|
||||||
|
static esp_err_t onewire_bus_uart_exchange_byte(onewire_bus_uart_obj_t *bus_uart, uint8_t tx_data, uint8_t *rx_data)
|
||||||
|
{
|
||||||
|
esp_err_t ret = uart_flush_input(bus_uart->uart_port_num);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (uart_tx_chars(bus_uart->uart_port_num, (const char *)&tx_data, 1) != 1) {
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
if (uart_read_bytes(bus_uart->uart_port_num, rx_data, 1, pdMS_TO_TICKS(ONEWIRE_UART_DEFAULT_TIMEOUT_MS)) != 1) {
|
||||||
|
return ESP_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_uart_write_bit_nolock(onewire_bus_uart_obj_t *bus_uart, uint8_t tx_bit)
|
||||||
|
{
|
||||||
|
uint8_t rx_data = 0;
|
||||||
|
const uint8_t tx_data = tx_bit ? ONEWIRE_UART_SLOT_TX_WRITE_1 : ONEWIRE_UART_SLOT_TX_WRITE_0;
|
||||||
|
esp_err_t ret = onewire_bus_uart_exchange_byte(bus_uart, tx_data, &rx_data);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return (rx_data == tx_data) ? ESP_OK : ESP_ERR_INVALID_STATE; // Check if the sent data is corrupted
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_uart_read_bit_nolock(onewire_bus_uart_obj_t *bus_uart, uint8_t *rx_bit)
|
||||||
|
{
|
||||||
|
uint8_t rx_data = 0;
|
||||||
|
esp_err_t ret = onewire_bus_uart_exchange_byte(bus_uart, ONEWIRE_UART_SLOT_TX_READ, &rx_data);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
*rx_bit = (rx_data == ONEWIRE_UART_SLOT_RX_READ_1) ? 1 : 0;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////// implementation of onewire_bus_t functions //////////////////////////////
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_uart_del(onewire_bus_handle_t bus)
|
||||||
|
{
|
||||||
|
onewire_bus_uart_obj_t *bus_uart = __containerof(bus, onewire_bus_uart_obj_t, base);
|
||||||
|
return onewire_bus_uart_destroy(bus_uart);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_uart_reset(onewire_bus_handle_t bus)
|
||||||
|
{
|
||||||
|
onewire_bus_uart_obj_t *bus_uart = __containerof(bus, onewire_bus_uart_obj_t, base);
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
uint8_t rx_data = 0;
|
||||||
|
|
||||||
|
xSemaphoreTake(bus_uart->bus_mutex, portMAX_DELAY);
|
||||||
|
ESP_GOTO_ON_ERROR(onewire_bus_uart_set_baud_rate(bus_uart, ONEWIRE_UART_BAUD_RESET), err, TAG, "set reset baudrate failed");
|
||||||
|
ESP_GOTO_ON_ERROR(onewire_bus_uart_exchange_byte(bus_uart, ONEWIRE_UART_RESET_TX, &rx_data), err, TAG, "create reset pulse failed");
|
||||||
|
ESP_GOTO_ON_FALSE(rx_data != ONEWIRE_UART_RESET_RX_NO_DEVICE, ESP_ERR_NOT_FOUND, err, TAG, "no 1-wire device found");
|
||||||
|
|
||||||
|
err:
|
||||||
|
xSemaphoreGive(bus_uart->bus_mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_uart_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size)
|
||||||
|
{
|
||||||
|
onewire_bus_uart_obj_t *bus_uart = __containerof(bus, onewire_bus_uart_obj_t, base);
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
ESP_RETURN_ON_FALSE(tx_data && tx_data_size, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
xSemaphoreTake(bus_uart->bus_mutex, portMAX_DELAY);
|
||||||
|
ESP_GOTO_ON_ERROR(onewire_bus_uart_set_baud_rate(bus_uart, ONEWIRE_UART_BAUD_SLOT), err, TAG, "set slot baudrate failed");
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < tx_data_size; i++) {
|
||||||
|
uint8_t current = tx_data[i];
|
||||||
|
for (int bit = 0; bit < 8; bit++) {
|
||||||
|
ESP_GOTO_ON_ERROR(onewire_bus_uart_write_bit_nolock(bus_uart, current & 0x01), err, TAG, "write bit failed");
|
||||||
|
current >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err:
|
||||||
|
xSemaphoreGive(bus_uart->bus_mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_uart_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size)
|
||||||
|
{
|
||||||
|
onewire_bus_uart_obj_t *bus_uart = __containerof(bus, onewire_bus_uart_obj_t, base);
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
ESP_RETURN_ON_FALSE(rx_buf && rx_buf_size, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
memset(rx_buf, 0, rx_buf_size);
|
||||||
|
xSemaphoreTake(bus_uart->bus_mutex, portMAX_DELAY);
|
||||||
|
ESP_GOTO_ON_ERROR(onewire_bus_uart_set_baud_rate(bus_uart, ONEWIRE_UART_BAUD_SLOT), err, TAG, "set slot baudrate failed");
|
||||||
|
|
||||||
|
for (size_t i = 0; i < rx_buf_size; i++) {
|
||||||
|
uint8_t current = 0;
|
||||||
|
for (int bit = 0; bit < 8; bit++) {
|
||||||
|
uint8_t rx_bit = 0;
|
||||||
|
ESP_GOTO_ON_ERROR(onewire_bus_uart_read_bit_nolock(bus_uart, &rx_bit), err, TAG, "read bit failed");
|
||||||
|
if (rx_bit) {
|
||||||
|
current |= (1 << bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rx_buf[i] = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
err:
|
||||||
|
xSemaphoreGive(bus_uart->bus_mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_uart_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit)
|
||||||
|
{
|
||||||
|
onewire_bus_uart_obj_t *bus_uart = __containerof(bus, onewire_bus_uart_obj_t, base);
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
xSemaphoreTake(bus_uart->bus_mutex, portMAX_DELAY);
|
||||||
|
ESP_GOTO_ON_ERROR(onewire_bus_uart_set_baud_rate(bus_uart, ONEWIRE_UART_BAUD_SLOT), err, TAG, "set slot baudrate failed");
|
||||||
|
ESP_GOTO_ON_ERROR(onewire_bus_uart_write_bit_nolock(bus_uart, tx_bit), err, TAG, "write bit failed");
|
||||||
|
err:
|
||||||
|
xSemaphoreGive(bus_uart->bus_mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t onewire_bus_uart_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit)
|
||||||
|
{
|
||||||
|
onewire_bus_uart_obj_t *bus_uart = __containerof(bus, onewire_bus_uart_obj_t, base);
|
||||||
|
ESP_RETURN_ON_FALSE(rx_bit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
xSemaphoreTake(bus_uart->bus_mutex, portMAX_DELAY);
|
||||||
|
ESP_GOTO_ON_ERROR(onewire_bus_uart_set_baud_rate(bus_uart, ONEWIRE_UART_BAUD_SLOT), err, TAG, "set slot baudrate failed");
|
||||||
|
ESP_GOTO_ON_ERROR(onewire_bus_uart_read_bit_nolock(bus_uart, rx_bit), err, TAG, "read bit failed");
|
||||||
|
err:
|
||||||
|
xSemaphoreGive(bus_uart->bus_mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "onewire_crc.h"
|
||||||
|
|
||||||
|
#define FAST_CRC 1 // define this to use the fast CRC table
|
||||||
|
|
||||||
|
#if FAST_CRC
|
||||||
|
|
||||||
|
static const uint8_t dalas_crc8_table[] = {
|
||||||
|
0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,
|
||||||
|
157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,
|
||||||
|
35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98,
|
||||||
|
190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,
|
||||||
|
70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7,
|
||||||
|
219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154,
|
||||||
|
101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,
|
||||||
|
248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,
|
||||||
|
140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205,
|
||||||
|
17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80,
|
||||||
|
175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238,
|
||||||
|
50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,
|
||||||
|
202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,
|
||||||
|
87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,
|
||||||
|
233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,
|
||||||
|
116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t onewire_crc8(uint8_t init_crc, uint8_t *input, size_t input_size)
|
||||||
|
{
|
||||||
|
uint8_t crc = init_crc;
|
||||||
|
for (size_t i = 0; i < input_size; i ++) {
|
||||||
|
crc = dalas_crc8_table[crc ^ input[i]];
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // FAST_CRC
|
||||||
|
|
||||||
|
uint8_t onewire_crc8(uint8_t init_crc, uint8_t *input, size_t input_size)
|
||||||
|
{
|
||||||
|
uint8_t crc = init_crc;
|
||||||
|
for (size_t i = 0; i < input_size; i++) {
|
||||||
|
uint8_t byte = input[i];
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
uint8_t x = (byte ^ crc) & 0x01;
|
||||||
|
crc >>= 1;
|
||||||
|
if (x != 0) {
|
||||||
|
crc ^= 0x8C;
|
||||||
|
}
|
||||||
|
byte >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // FAST_CRC
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "esp_check.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "onewire_bus.h"
|
||||||
|
#include "onewire_device.h"
|
||||||
|
#include "onewire_crc.h"
|
||||||
|
#include "onewire_cmd.h"
|
||||||
|
|
||||||
|
static const char *TAG = "1-wire.device";
|
||||||
|
|
||||||
|
typedef struct onewire_device_iter_t {
|
||||||
|
onewire_bus_handle_t bus;
|
||||||
|
uint16_t last_discrepancy;
|
||||||
|
bool is_last_device;
|
||||||
|
uint8_t rom_number[sizeof(onewire_device_address_t)];
|
||||||
|
} onewire_device_iter_t;
|
||||||
|
|
||||||
|
esp_err_t onewire_new_device_iter(onewire_bus_handle_t bus, onewire_device_iter_handle_t *ret_iter)
|
||||||
|
{
|
||||||
|
ESP_RETURN_ON_FALSE(bus && ret_iter, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
|
||||||
|
onewire_device_iter_t *iter = calloc(1, sizeof(onewire_device_iter_t));
|
||||||
|
ESP_RETURN_ON_FALSE(iter, ESP_ERR_NO_MEM, TAG, "no mem for device iterator");
|
||||||
|
|
||||||
|
iter->bus = bus;
|
||||||
|
*ret_iter = iter;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t onewire_del_device_iter(onewire_device_iter_handle_t iter)
|
||||||
|
{
|
||||||
|
ESP_RETURN_ON_FALSE(iter, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
|
||||||
|
free(iter);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search algorithm inspired by https://www.analog.com/en/app-notes/1wire-search-algorithm.html
|
||||||
|
esp_err_t onewire_device_iter_get_next(onewire_device_iter_handle_t iter, onewire_device_t *dev)
|
||||||
|
{
|
||||||
|
ESP_RETURN_ON_FALSE(iter && dev, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||||
|
// we don't treat iterator ending and ESP_ERR_NOT_FOUND as an error condition, so just print debug message here
|
||||||
|
if (iter->is_last_device) {
|
||||||
|
ESP_LOGD(TAG, "1-wire rom search finished");
|
||||||
|
return ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
onewire_bus_handle_t bus = iter->bus;
|
||||||
|
esp_err_t reset_result = onewire_bus_reset(bus);
|
||||||
|
if (reset_result == ESP_ERR_NOT_FOUND) {
|
||||||
|
ESP_LOGW(TAG, "reset bus failed: no devices found");
|
||||||
|
return ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
ESP_RETURN_ON_ERROR(reset_result, TAG, "reset bus failed");
|
||||||
|
|
||||||
|
// send rom search command and start search algorithm
|
||||||
|
ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(bus, (uint8_t[]) {
|
||||||
|
ONEWIRE_CMD_SEARCH_NORMAL
|
||||||
|
}, 1), TAG, "send ONEWIRE_CMD_SEARCH_NORMAL failed");
|
||||||
|
|
||||||
|
uint8_t last_zero = 0;
|
||||||
|
for (uint16_t rom_bit_index = 0; rom_bit_index < sizeof(onewire_device_address_t) * 8; rom_bit_index ++) {
|
||||||
|
uint8_t rom_byte_index = rom_bit_index / 8;
|
||||||
|
uint8_t rom_bit_mask = 1 << (rom_bit_index % 8); // calculate byte index and bit mask in advance for convenience
|
||||||
|
|
||||||
|
uint8_t rom_bit = 0;
|
||||||
|
uint8_t rom_bit_complement = 0;
|
||||||
|
ESP_RETURN_ON_ERROR(onewire_bus_read_bit(bus, &rom_bit), TAG, "read rom_bit error"); // write 1 bit to read from the bus
|
||||||
|
ESP_RETURN_ON_ERROR(onewire_bus_read_bit(bus, &rom_bit_complement), TAG, "read rom_bit_complement error"); // read a bit and its complement
|
||||||
|
|
||||||
|
// No devices participating in search.
|
||||||
|
if (rom_bit && rom_bit_complement) {
|
||||||
|
ESP_LOGE(TAG, "no devices participating in search");
|
||||||
|
return ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t search_direction;
|
||||||
|
if (rom_bit != rom_bit_complement) { // There are only 0s or 1s in the bit of the participating ROM numbers.
|
||||||
|
search_direction = rom_bit; // just go ahead
|
||||||
|
} else { // There are both 0s and 1s in the current bit position of the participating ROM numbers. This is a discrepancy.
|
||||||
|
if (rom_bit_index < iter->last_discrepancy) { // current id bit is before the last discrepancy bit
|
||||||
|
search_direction = (iter->rom_number[rom_byte_index] & rom_bit_mask) ? 0x01 : 0x00; // follow previous way
|
||||||
|
} else {
|
||||||
|
search_direction = (rom_bit_index == iter->last_discrepancy) ? 0x01 : 0x00; // search for 0 bit first
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search_direction == 0) { // record zero's position in last zero
|
||||||
|
last_zero = rom_bit_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search_direction == 1) { // set corresponding rom bit by search direction
|
||||||
|
iter->rom_number[rom_byte_index] |= rom_bit_mask;
|
||||||
|
} else {
|
||||||
|
iter->rom_number[rom_byte_index] &= ~rom_bit_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set search direction
|
||||||
|
ESP_RETURN_ON_ERROR(onewire_bus_write_bit(bus, search_direction), TAG, "write direction bit error");
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the search was successful
|
||||||
|
iter->last_discrepancy = last_zero;
|
||||||
|
if (iter->last_discrepancy == 0) { // last zero loops back to the first bit
|
||||||
|
iter->is_last_device = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check crc
|
||||||
|
ESP_RETURN_ON_FALSE(onewire_crc8(0, iter->rom_number, 7) == iter->rom_number[7], ESP_ERR_INVALID_CRC, TAG, "bad device crc");
|
||||||
|
|
||||||
|
// save the ROM number as the device address
|
||||||
|
memcpy(&dev->address, iter->rom_number, sizeof(onewire_device_address_t));
|
||||||
|
dev->bus = bus;
|
||||||
|
ESP_LOGD(TAG, "new 1-Wire device found, address: %016llX", dev->address);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
set(COMPONENTS main)
|
||||||
|
project(onewire_bus_test)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
idf_component_register(SRCS "onewire_bus_test.c"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
PRIV_REQUIRES unity)
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
menu "Example Configuration"
|
||||||
|
|
||||||
|
choice EXAMPLE_ONEWIRE_BACKEND
|
||||||
|
prompt "1-Wire backend"
|
||||||
|
default EXAMPLE_ONEWIRE_BACKEND_RMT if SOC_RMT_SUPPORTED
|
||||||
|
default EXAMPLE_ONEWIRE_BACKEND_UART if SOC_UART_SUPPORTED
|
||||||
|
help
|
||||||
|
Select which low-level backend the test app uses.
|
||||||
|
|
||||||
|
config EXAMPLE_ONEWIRE_BACKEND_RMT
|
||||||
|
bool "RMT backend"
|
||||||
|
depends on SOC_RMT_SUPPORTED
|
||||||
|
|
||||||
|
config EXAMPLE_ONEWIRE_BACKEND_UART
|
||||||
|
bool "UART backend"
|
||||||
|
depends on SOC_UART_SUPPORTED
|
||||||
|
endchoice
|
||||||
|
|
||||||
|
config EXAMPLE_ONEWIRE_UART_PORT_NUM
|
||||||
|
int "UART port number"
|
||||||
|
depends on EXAMPLE_ONEWIRE_BACKEND_UART
|
||||||
|
default 1
|
||||||
|
range 0 2
|
||||||
|
help
|
||||||
|
UART port used by UART backend.
|
||||||
|
Valid UART port numbers differ across targets.
|
||||||
|
Note: UART0 is typically used by the console output.
|
||||||
|
|
||||||
|
config EXAMPLE_ONEWIRE_BUS_GPIO
|
||||||
|
int "1-Wire data GPIO"
|
||||||
|
default 0
|
||||||
|
range 0 63
|
||||||
|
help
|
||||||
|
GPIO number used for the 1-Wire data line.
|
||||||
|
Valid GPIO numbers depend on the selected target; choose a
|
||||||
|
data-capable GPIO within this range for your chip.
|
||||||
|
|
||||||
|
config EXAMPLE_ONEWIRE_ENABLE_INTERNAL_PULLUP
|
||||||
|
bool "Enable internal pull-up"
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
Enable internal pull-up resistor on the GPIO pin used for the 1-Wire data line.
|
||||||
|
This is useful when no external pull-up resistor is present.
|
||||||
|
|
||||||
|
config EXAMPLE_ONEWIRE_MAX_DEVICES
|
||||||
|
int "Maximum devices to search"
|
||||||
|
default 2
|
||||||
|
range 1 64
|
||||||
|
help
|
||||||
|
Maximum number of devices to search on the 1-Wire bus.
|
||||||
|
This test app performs SEARCH ROM to collect device addresses (64-bit ROM IDs).
|
||||||
|
|
||||||
|
endmenu
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
dependencies:
|
||||||
|
espressif/onewire_bus:
|
||||||
|
version: "*"
|
||||||
|
override_path: "../.."
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
|
*/
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_check.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#include "onewire_bus.h"
|
||||||
|
#include "onewire_device.h"
|
||||||
|
|
||||||
|
static const char *TAG = "test-app";
|
||||||
|
|
||||||
|
#if CONFIG_EXAMPLE_ONEWIRE_ENABLE_INTERNAL_PULLUP
|
||||||
|
#define EXAMPLE_ONEWIRE_ENABLE_INTERNAL_PULLUP 1
|
||||||
|
#else
|
||||||
|
#define EXAMPLE_ONEWIRE_ENABLE_INTERNAL_PULLUP 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CONFIG_EXAMPLE_ONEWIRE_BACKEND_UART
|
||||||
|
#define EXAMPLE_ONEWIRE_UART_PORT_NUM CONFIG_EXAMPLE_ONEWIRE_UART_PORT_NUM
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define EXAMPLE_ONEWIRE_BUS_GPIO CONFIG_EXAMPLE_ONEWIRE_BUS_GPIO
|
||||||
|
#define EXAMPLE_ONEWIRE_MAX_DEVICES CONFIG_EXAMPLE_ONEWIRE_MAX_DEVICES
|
||||||
|
|
||||||
|
void app_main(void)
|
||||||
|
{
|
||||||
|
// install new 1-wire bus
|
||||||
|
onewire_bus_handle_t bus;
|
||||||
|
onewire_bus_config_t bus_config = {
|
||||||
|
.bus_gpio_num = EXAMPLE_ONEWIRE_BUS_GPIO,
|
||||||
|
.flags = {
|
||||||
|
.en_pull_up = EXAMPLE_ONEWIRE_ENABLE_INTERNAL_PULLUP,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#if CONFIG_EXAMPLE_ONEWIRE_BACKEND_RMT
|
||||||
|
onewire_bus_rmt_config_t rmt_config = {
|
||||||
|
.max_rx_bytes = 10, // 1byte ROM command + 8byte ROM number + 1byte device command
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(onewire_new_bus_rmt(&bus_config, &rmt_config, &bus));
|
||||||
|
ESP_LOGI(TAG, "1-Wire bus installed on GPIO%d by RMT backend", EXAMPLE_ONEWIRE_BUS_GPIO);
|
||||||
|
#elif CONFIG_EXAMPLE_ONEWIRE_BACKEND_UART
|
||||||
|
onewire_bus_uart_config_t uart_config = {
|
||||||
|
.uart_port_num = EXAMPLE_ONEWIRE_UART_PORT_NUM,
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(onewire_new_bus_uart(&bus_config, &uart_config, &bus));
|
||||||
|
ESP_LOGI(TAG, "1-Wire bus installed on GPIO%d by UART backend (UART%d)",
|
||||||
|
EXAMPLE_ONEWIRE_BUS_GPIO, EXAMPLE_ONEWIRE_UART_PORT_NUM);
|
||||||
|
#else
|
||||||
|
#error "No 1-Wire backend selected in menuconfig"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int onewire_device_found = 0;
|
||||||
|
onewire_device_iter_handle_t iter = NULL;
|
||||||
|
onewire_device_t next_onewire_device;
|
||||||
|
esp_err_t search_result = ESP_OK;
|
||||||
|
|
||||||
|
// create 1-wire device iterator, which is used for device search
|
||||||
|
ESP_ERROR_CHECK(onewire_new_device_iter(bus, &iter));
|
||||||
|
ESP_LOGI(TAG, "Device iterator created, start searching...");
|
||||||
|
do {
|
||||||
|
search_result = onewire_device_iter_get_next(iter, &next_onewire_device);
|
||||||
|
// found a new device
|
||||||
|
if (search_result == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Found a new device, address: %016llX", next_onewire_device.address);
|
||||||
|
onewire_device_found++;
|
||||||
|
if (onewire_device_found >= EXAMPLE_ONEWIRE_MAX_DEVICES) {
|
||||||
|
ESP_LOGI(TAG, "Max device number reached, stop searching...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (search_result != ESP_ERR_NOT_FOUND);
|
||||||
|
ESP_ERROR_CHECK(onewire_del_device_iter(iter));
|
||||||
|
ESP_LOGI(TAG, "Searching done, %d device(s) found", onewire_device_found);
|
||||||
|
|
||||||
|
// delete the bus
|
||||||
|
ESP_LOGI(TAG, "Deleting bus...");
|
||||||
|
ESP_ERROR_CHECK(onewire_bus_del(bus));
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||||
|
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
|
import pytest
|
||||||
|
from pytest_embedded import Dut
|
||||||
|
|
||||||
|
@pytest.mark.generic
|
||||||
|
@pytest.mark.parametrize('config', ['rmt', 'uart'], indirect=True)
|
||||||
|
def test_onewire_bus(dut: Dut, config: str) -> None:
|
||||||
|
if config == 'rmt':
|
||||||
|
dut.expect_exact('test-app: 1-Wire bus installed on GPIO0 by RMT backend')
|
||||||
|
elif config == 'uart':
|
||||||
|
dut.expect_exact('test-app: 1-Wire bus installed on GPIO0 by UART backend (UART1)')
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Unknown test config: {config}')
|
||||||
|
dut.expect_exact('test-app: Device iterator created, start searching')
|
||||||
|
dut.expect_exact('test-app: Searching done')
|
||||||
7
esp32/partitions.csv
Normal file
7
esp32/partitions.csv
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Name, Type, SubType, Offset, Size
|
||||||
|
nvs, data, nvs, 0x9000, 0x5000
|
||||||
|
otadata, data, ota, 0xe000, 0x2000
|
||||||
|
phy_init, data, phy, 0x10000, 0x1000
|
||||||
|
|
||||||
|
ota_0, app, ota_0, 0x20000, 0x140000
|
||||||
|
ota_1, app, ota_1, 0x160000, 0x140000
|
||||||
|
1
esp32/version.txt
Normal file
1
esp32/version.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
Reference in New Issue
Block a user