diff --git a/build.sbt b/build.sbt index 92a310e2..aab89c32 100644 --- a/build.sbt +++ b/build.sbt @@ -152,7 +152,7 @@ lazy val ce3 = (project in file("ce3")) "io.kubernetes" % "client-java-api" % "20.0.1", "io.kubernetes" % "client-java" % "20.0.1", "jakarta.mail" % "jakarta.mail-api" % "2.1.3", - "io.scalaland" %% "chimney" % "1.1.0", + "io.scalaland" %% "chimney" % "1.4.0", "org.tpolecat" %% "skunk-core" % "0.6.4", "io.7mind.izumi" %% "logstage-core" % "1.2.10", "org.tpolecat" %% "doobie-core" % "1.0.0-RC2", diff --git a/ce3/src/main/scala/el_meter/Sandbox.scala b/ce3/src/main/scala/el_meter/Sandbox.scala index a2cea643..c42a6962 100644 --- a/ce3/src/main/scala/el_meter/Sandbox.scala +++ b/ce3/src/main/scala/el_meter/Sandbox.scala @@ -2,48 +2,41 @@ package el_meter import cats.effect.IO import cats.effect.unsafe.implicits.global -import cats.implicits.catsSyntaxApplicativeId -import org.http4s.Header -import org.http4s.MediaType +import cats.implicits._ +import io.scalaland.chimney.Transformer +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneOffset import org.http4s.Method import org.http4s.Request +import org.http4s.Status.BadRequest +import org.http4s.Status.ClientError import org.http4s.Status.Successful +import org.http4s.Status.TooManyRequests import org.http4s.blaze.client.BlazeClientBuilder import org.http4s.circe.CirceEntityCodec.circeEntityDecoder import org.http4s.implicits.http4sLiteralsSyntax import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -import org.typelevel.ci.CIStringSyntax +import scala.concurrent.duration.DurationInt object Http { - private def mkHttpClient = + def mkHttpClient = BlazeClientBuilder[IO].resource private val url = uri"http://192.168.7.38?page=getdata&devid=1828726629&devpass=6543" - import org.http4s.headers._ val rq = Request[IO](Method.GET, url) - .withHeaders( - `Accept`(MediaType.application.json), - Header.Raw(ci"User-Agent", "IntelliJ HTTP Client/IntelliJ IDEA 2024.2"), - Header.Raw(ci"Accept-Encoding", "br, deflate, gzip, x-gzip"), -// Header.Raw(ci"Accept", "*/*"), - Header.Raw(ci"content-length", "0"), - ) import model._ def getData = mkHttpClient .flatMap(_.run(rq)) .use { -// case x => x.body.compile.count.flatMap(x => IO(pprint.log(x))) >> - case Successful(rs) => rs.attemptAs[RawResponse].value - .flatMap { - case Right(x) => x.pure[IO] - case Left(x) => IO(pprint.log(x)) - } - case x => IO(pprint.log(x)) >> IO.raiseError(new RuntimeException("wrong response")) + case Successful(rs) => rs.as[RawResponse].map(_.some) + case ClientError(rs) if rs.status == TooManyRequests => IO(None) + case x => IO(pprint.log(x)) >> IO.raiseError(new RuntimeException("wrong response")) } } @@ -51,11 +44,45 @@ object Http { class SandboxSpec extends AnyFunSuite with Matchers with ScalaCheckPropertyChecks { import Http._ + import io.scalaland.chimney.inlined._ + import model._ +// import io.scalaland.chimney.dsl._ + + test("round") { + pprint.log(round1(1.23456)) + } + + test("instant") { + val raw = "1724506617" + val ldt = LocalDateTime.ofInstant( + Instant.ofEpochSecond(raw.toInt), + ZoneOffset.ofHours(3) + ) + pprint.log(ldt) - test("1") { + } + + test("one value") { getData + .map(_.getOrElse(???)) + .map(_.into[DataLine].transform) + .map(_.into[DataLineWattOnlyDetailed].transform) .flatMap(x => IO(pprint.log(x))) .unsafeRunSync() } + test("streamed") { + fs2.Stream + .awakeEvery[IO](5000.millis) + .evalMap(_ => getData) + .unNone + .map(_.into[DataLine].transform) + .map(_.into[DataLineWattOnlyDetailed].transform) + .map(_.into[DataLineWattOnlyShort].transform) + .evalTap(x => IO(pprint.log(x))) + .compile + .drain + .unsafeRunSync() + } + } diff --git a/ce3/src/main/scala/el_meter/model.scala b/ce3/src/main/scala/el_meter/model.scala index 7d4b411d..7110445f 100644 --- a/ce3/src/main/scala/el_meter/model.scala +++ b/ce3/src/main/scala/el_meter/model.scala @@ -1,6 +1,10 @@ package el_meter import io.circe.generic.AutoDerivation +import io.scalaland.chimney.Transformer +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneOffset object model { @@ -60,4 +64,79 @@ object model { ) object RawResponse extends AutoDerivation + case class Item( + name: String, + unit: String, + value: Double + ) + + case class Data( + V1: Item, + A1: Item, + W1: Item, + PF1: Item, + // + V2: Item, + A2: Item, + W2: Item, + PF2: Item, + // + V3: Item, + A3: Item, + W3: Item, + PF3: Item, + // + A: Item, + W: Item, + ) + + case class DataLine( + time: LocalDateTime, + data: Data + ) + + case class DataWattOnly( + W1: Item, + W2: Item, + W3: Item, + W: Item, + ) + case class DataLineWattOnlyDetailed( + time: LocalDateTime, + data: DataWattOnly + ) + + case class DataLineWattOnlyShort( + time: LocalDateTime, + W1: Double, + W2: Double, + W3: Double, + W: Double, + ) + + def round1(x: Double): Double = (x * 10).round.toDouble / 10 + + implicit val tr0: Transformer[String, LocalDateTime] = (src: String) => + LocalDateTime.ofInstant( + Instant.ofEpochSecond(src.toInt), + ZoneOffset.ofHours(3) + ) + + implicit val tr1: Transformer[RawItem, Item] = Transformer.define[RawItem, Item] + .withFieldComputed(_.value, r => round1(r.value.toDouble)) + .buildTransformer + + implicit val tr2: Transformer[RawData, Data] = Transformer.derive[RawData, Data] + + implicit val tr3: Transformer[Data, DataWattOnly] = Transformer.derive[Data, DataWattOnly] + + implicit val tr4: Transformer[DataLine, DataLineWattOnlyDetailed] = Transformer.derive[DataLine, DataLineWattOnlyDetailed] + + implicit val tr6 = Transformer.define[DataLineWattOnlyDetailed, DataLineWattOnlyShort] + .withFieldComputed(_.W1, _.data.W1.value) + .withFieldComputed(_.W2, _.data.W2.value) + .withFieldComputed(_.W3, _.data.W3.value) + .withFieldComputed(_.W, _.data.W.value) + .buildTransformer + } diff --git a/ce3/src/main/scala/trigonometry/Trigonometry.scala b/ce3/src/main/scala/trigonometry/Trigonometry.scala new file mode 100644 index 00000000..677d0d7e --- /dev/null +++ b/ce3/src/main/scala/trigonometry/Trigonometry.scala @@ -0,0 +1,69 @@ +package trigonometry + +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks + +class Trigonometry extends AnyFunSuite with Matchers with ScalaCheckPropertyChecks { + + import scala.math._ + + def round(x: Double, nDigits: Int): Double = { + val k = pow(10, nDigits) + (x * k).round.toDouble / k + } + def round1(x: Double): Double = round(x, 1) + def round2(x: Double): Double = round(x, 2) + + test("degree => radians") { + Seq( + 0, // 0 + 45, // Pi/4 + 90, // Pi/2 + 180, // Pi + 360, // 2*Pi + ) + .map(_.toRadians) + .foreach(x => pprint.log(x)) + } + + test("radians => degree") { + Seq( + 0, // 0 + Pi / 4, // 45 + Pi / 2, // 90 + Pi, // 180 + 2 * Pi, // 360 + ) + .map(_.toDegrees) + .foreach(x => pprint.log(x)) + } + + test("sin, cos, tan requires radians") { + Seq( + 0, + 45, // 1/sqrt(2) + 90, + 180, + 270, + 360, + ) + .map(x => (x, x.toRadians)) + .map { case x @ (_, r) => x -> (sin(r), cos(r), tan(r)) } + .foreach(x => pprint.log(x)) + } + + test("asin, acos return radians") { + Seq( + 1.0, // 0 + 0.9, // 25 + 0.8, // 37 + 0.7, // 45 + 0.5, // 60 + ) + .map(x => acos(x)) + .map(_.toDegrees) + .foreach(x => pprint.log(x)) + } + +} diff --git a/ce3/src/main/scala/trigonometry/triginometry.md b/ce3/src/main/scala/trigonometry/triginometry.md new file mode 100644 index 00000000..346fbc32 --- /dev/null +++ b/ce3/src/main/scala/trigonometry/triginometry.md @@ -0,0 +1,6 @@ +1. $$Radians = Degrees \times {π \over 180}$$ +2. $$Degrees = Radians \times {180 \over π}$$ +3. $$1^{\circ} = {π \over 180} radians = 0.0174533 radians$$ +4. $$90^{\circ} = {π \over 2} radians = 1.5708 radians$$ +5. $$180^{\circ} = π radians = 3.1416 radians$$ +6. $$360^{\circ} = 2 \times π radians = 3.1416 radians$$