From fede52dd60ae6ad4321dabaf1c96c06dc5e7749e Mon Sep 17 00:00:00 2001 From: Mikko Ahlroth Date: Sat, 6 Jul 2024 00:17:28 +0300 Subject: [PATCH] Working process.call --- backend/gleam.toml | 4 +- backend/manifest.toml | 10 +++-- backend/src/ap_systems/api.gleam | 42 ++++++++++++++----- backend/src/aurinko.gleam | 34 ---------------- backend/src/aurinko/updater.gleam | 49 +++++++++++++---------- backend/src/aurinko/updater/pubsub.gleam | 16 ++++++++ backend/src/aurinko/updater/types.gleam | 23 +++++++++++ backend/src/aurinko/web.gleam | 7 +++- backend/src/aurinko_backend.gleam | 51 ++++++++++++++++++++++++ 9 files changed, 167 insertions(+), 69 deletions(-) delete mode 100644 backend/src/aurinko.gleam create mode 100644 backend/src/aurinko/updater/pubsub.gleam create mode 100644 backend/src/aurinko/updater/types.gleam create mode 100644 backend/src/aurinko_backend.gleam diff --git a/backend/gleam.toml b/backend/gleam.toml index 113887b..b704edd 100644 --- a/backend/gleam.toml +++ b/backend/gleam.toml @@ -27,9 +27,11 @@ htmgrrrl = ">= 0.3.0 and < 1.0.0" form_coder = ">= 0.3.0 and < 1.0.0" birl = ">= 1.7.1 and < 2.0.0" gleam_json = ">= 1.0.1 and < 2.0.0" -envoy = ">= 1.0.1 and < 2.0.0" mist = ">= 1.2.0 and < 2.0.0" aurinko_common = { path = "../common" } gleamy_structures = ">= 1.0.0 and < 2.0.0" +glubsub = ">= 1.0.1 and < 2.0.0" +dot_env = ">= 1.0.0 and < 2.0.0" +chip = ">= 0.7.5 and < 1.0.0" [dev-dependencies] diff --git a/backend/manifest.toml b/backend/manifest.toml index a4ef84a..2131461 100644 --- a/backend/manifest.toml +++ b/backend/manifest.toml @@ -5,7 +5,8 @@ packages = [ { name = "aurinko_common", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "../common" }, { name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" }, { name = "biscotto", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "biscotto", source = "hex", outer_checksum = "A1CFEA1686FA8ABDE90B76E22775FF29EE8156A64DAC327F48141A68951D662C" }, - { name = "envoy", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "CFAACCCFC47654F7E8B75E614746ED924C65BD08B1DE21101548AC314A8B6A41" }, + { name = "chip", version = "0.7.5", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "chip", source = "hex", outer_checksum = "1C50EB65CE8617D64FBCCE9A3EA7E6C7BF3E646D8E39B92EA74F68B67476F59D" }, + { name = "dot_env", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "simplifile"], otp_app = "dot_env", source = "hex", outer_checksum = "E7B84DC7B579553AF3B9F0A03B2F8DDB9B44521F553CCFBE633AA595C27F1A05" }, { name = "esqlite", version = "0.8.8", build_tools = ["rebar3"], requirements = [], otp_app = "esqlite", source = "hex", outer_checksum = "374902457C7D94DC9409C98D3BDD1CA0D50A60DC9F3BDF1FD8EB74C0DCDF02D6" }, { name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" }, { name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" }, @@ -19,6 +20,7 @@ packages = [ { name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" }, { name = "gleamy_structures", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleamy_structures", source = "hex", outer_checksum = "FF1B7600123B2B7C15E91BEBE6F417B4003928BF4282EF01A74A792B4ED6985A" }, { name = "glisten", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "CF3A9383E9BA4A8CBAF2F7B799716290D02F2AC34E7A77556B49376B662B9314" }, + { name = "glubsub", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glubsub", source = "hex", outer_checksum = "82563C2A7B5354D58FA2BEA88EF021DEB0F22189043668A604902034F5E2E81D" }, { name = "gramps", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "3CCAA6E081225180D95C79679D383BBF51C8D1FDC1B84DA1DA444F628C373793" }, { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, { name = "htmerl", version = "0.1.0", build_tools = ["rebar3"], requirements = [], otp_app = "htmerl", source = "hex", outer_checksum = "D932F76EA33C318A79F41A429FDBEAE30820390DDB72BA89F38EEF32FEC36395" }, @@ -38,7 +40,8 @@ packages = [ aurinko_common = { path = "../common" } birl = { version = ">= 1.7.1 and < 2.0.0" } biscotto = { version = ">= 1.0.0 and < 2.0.0" } -envoy = { version = ">= 1.0.1 and < 2.0.0" } +chip = { version = ">= 0.7.5 and < 1.0.0"} +dot_env = { version = ">= 1.0.0 and < 2.0.0" } form_coder = { version = ">= 0.3.0 and < 1.0.0" } gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" } gleam_http = { version = ">= 3.6.0 and < 4.0.0" } @@ -46,7 +49,8 @@ gleam_httpc = { version = ">= 2.2.0 and < 3.0.0" } gleam_json = { version = ">= 1.0.1 and < 2.0.0" } gleam_otp = { version = ">= 0.10.0 and < 1.0.0" } gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } -gleamy_structures = { version = ">= 1.0.0 and < 2.0.0"} +gleamy_structures = { version = ">= 1.0.0 and < 2.0.0" } +glubsub = { version = ">= 1.0.1 and < 2.0.0" } htmgrrrl = { version = ">= 0.3.0 and < 1.0.0" } lustre = { version = ">= 4.3.0 and < 5.0.0" } mist = { version = ">= 1.2.0 and < 2.0.0" } diff --git a/backend/src/ap_systems/api.gleam b/backend/src/ap_systems/api.gleam index 6f06114..d956ad3 100644 --- a/backend/src/ap_systems/api.gleam +++ b/backend/src/ap_systems/api.gleam @@ -1,13 +1,14 @@ import birl import biscotto import form_coder +import gleam/bool import gleam/dynamic import gleam/float import gleam/http import gleam/http/request +import gleam/http/response import gleam/httpc import gleam/int -import gleam/io import gleam/json import gleam/list import gleam/option @@ -19,6 +20,8 @@ import htmgrrrl pub type APIError { RequestError(err: dynamic.Dynamic) DecodeError(err: json.DecodeError) + NoContentTypeError + ContentTypeError(wrong_content_type: String) ScrapeError } @@ -88,25 +91,37 @@ pub fn login(username: String, password: String) { pub fn get_production(cookies: biscotto.CookieJar) { let assert Ok(req) = request.from_uri(production_info_url()) - use resp <- result.try( + let req = req |> request.set_method(http.Post) |> set_common_headers() |> biscotto.with_cookies(cookies) + + use resp <- result.try( + req |> httpc.send() |> result.map_error(RequestError), ) + use content_type <- result.try( + response.get_header(resp, "content-type") + |> result.replace_error(NoContentTypeError), + ) + use <- bool.guard( + !string.contains(content_type, "application/json"), + Error(ContentTypeError(content_type)), + ) + json.decode( resp.body, dynamic.decode7( ProductionInfo, dynamic.field("co2", float_string_decoder), dynamic.field("duration", dynamic.int), - dynamic.field("ecu_sign", dynamic.string), - dynamic.field("last_power", float_string_decoder), + dynamic.field("ecuSign", dynamic.string), + dynamic.field("lastPower", float_string_decoder), dynamic.field("lifetime", float_string_decoder), - dynamic.field("meter_flag", dynamic.int), + dynamic.field("meterFlag", dynamic.int), dynamic.field("today", float_string_decoder), ), ) @@ -215,7 +230,6 @@ pub fn get_module_power(cookies: biscotto.CookieJar, modules: ModuleIDs) { ) |> set_common_headers() |> biscotto.with_cookies(cookies) - |> io.debug() |> httpc.send() |> result.map_error(RequestError) @@ -271,8 +285,16 @@ fn module_power_url() { fn float_string_decoder(str: dynamic.Dynamic) { use value <- result.try(dynamic.string(str)) - float.parse(value) - |> result.replace_error([ - dynamic.DecodeError("a string representing a float", value, []), - ]) + + case float.parse(value) { + Ok(f) -> Ok(f) + Error(_) -> + case int.parse(value) { + Ok(i) -> Ok(int.to_float(i)) + Error(_) -> + Error([ + dynamic.DecodeError("a string representing a float", value, []), + ]) + } + } } diff --git a/backend/src/aurinko.gleam b/backend/src/aurinko.gleam deleted file mode 100644 index 8b3ab4d..0000000 --- a/backend/src/aurinko.gleam +++ /dev/null @@ -1,34 +0,0 @@ -import ap_systems/api -import ap_systems/module_power -import aurinko/web -import envoy -import gleam/erlang/process -import gleam/int -import gleam/io -import gleam/result -import gleam/string - -pub fn main() { - let assert Ok(username) = envoy.get("USERNAME") - let assert Ok(password) = envoy.get("PASSWORD") - let assert Ok(port) = envoy.get("PORT") - let assert Ok(port) = int.parse(port) - let assert Ok(secret_key_base) = envoy.get("SECRET_KEY_BASE") - - let assert Ok(_) = web.init(port, secret_key_base) - process.sleep_forever() - // use cookies <- result.try( - // api.login(username, password) |> result.map_error(string.inspect), - // ) - - // io.debug(cookies) - // io.debug(api.get_production(cookies) |> result.map_error(string.inspect)) - // use ids <- result.try( - // io.debug(api.get_module_ids(cookies)) |> result.map_error(string.inspect), - // ) - // use module_data <- result.try( - // api.get_module_power(cookies, ids) |> result.map_error(string.inspect), - // ) - // io.debug(module_power.decode_api_data(module_data)) - // |> result.map_error(string.inspect) -} diff --git a/backend/src/aurinko/updater.gleam b/backend/src/aurinko/updater.gleam index 2f4ad3b..7361575 100644 --- a/backend/src/aurinko/updater.gleam +++ b/backend/src/aurinko/updater.gleam @@ -1,5 +1,9 @@ import ap_systems/api import ap_systems/module_power +import aurinko/updater/pubsub +import aurinko/updater/types.{ + type Dataset, type ModuleDataset, Dataset, ModuleDataset, ModulePower, +} import birl import birl/duration import biscotto @@ -13,37 +17,25 @@ import gleam/order import gleam/otp/actor import gleam/result import gleamy/red_black_tree_set.{type Set} +import glubsub const update_interval = 10_000 const login_cookie_expiry = 86_400 +pub type GetDataResponse { + GetDataResponse(dataset: option.Option(Dataset)) +} + pub type Message { Update PeriodicUpdate + GetData(reply_subject: process.Subject(GetDataResponse)) } -pub type ProductionInfo = - api.ProductionInfo - pub type ModuleIDs = api.ModuleIDs -pub type MomentaryPower = - module_power.MomentaryPower - -pub type ModulePower { - ModulePower(id: String, power: dict.Dict(birl.Time, MomentaryPower)) -} - -pub type ModuleDataset { - ModuleDataset(times: Set(birl.Time), datas: dict.Dict(String, ModulePower)) -} - -pub type Dataset { - Dataset(production_info: ProductionInfo, modules: ModuleDataset) -} - pub type Credentials { Credentials(username: String, password: String) } @@ -71,6 +63,7 @@ pub type State { inner: InnerState, error: option.Option(UpdateError), self_subject: process.Subject(Message), + pubsub: pubsub.UpdaterPubSub, ) } @@ -79,7 +72,11 @@ pub type UpdateError { DecodeError(err: dynamic.DecodeError) } -pub fn start(username: String, password: String) { +pub fn get_data(subject: process.Subject(Message)) { + process.call(subject, fn(reply_subject) { GetData(reply_subject) }, 5000) +} + +pub fn start(username: String, password: String, pubsub: pubsub.UpdaterPubSub) { let spec = actor.Spec( init: fn() { @@ -95,6 +92,7 @@ pub fn start(username: String, password: String) { Pending, option.None, self_subject, + pubsub, ), selector: selector, ) @@ -118,15 +116,26 @@ fn handle_message(message: Message, state: State) -> actor.Next(Message, State) ) Nil } - Update -> Nil + _ -> Nil } case message { Update | PeriodicUpdate -> { use new_state <- result.try(update(credentials, inner_state)) + let _ = case new_state.dataset { + option.Some(dataset) -> + glubsub.broadcast(state.pubsub.out, pubsub.NewData(dataset)) + option.None -> Ok(Nil) + } + Ok(actor.continue(State(..state, inner: Initialised(new_state)))) } + + GetData(reply_subject) -> { + process.send(reply_subject, GetDataResponse(inner_state.dataset)) + Ok(actor.continue(state)) + } } }) diff --git a/backend/src/aurinko/updater/pubsub.gleam b/backend/src/aurinko/updater/pubsub.gleam new file mode 100644 index 0000000..b7ae110 --- /dev/null +++ b/backend/src/aurinko/updater/pubsub.gleam @@ -0,0 +1,16 @@ +import aurinko/updater/types +import gleam/result +import glubsub + +pub type OutMessage { + NewData(dataset: types.Dataset) +} + +pub type UpdaterPubSub { + UpdaterPubSub(out: glubsub.Topic(OutMessage)) +} + +pub fn init() { + use out <- result.try(glubsub.new_topic()) + Ok(UpdaterPubSub(out)) +} diff --git a/backend/src/aurinko/updater/types.gleam b/backend/src/aurinko/updater/types.gleam new file mode 100644 index 0000000..c3d3536 --- /dev/null +++ b/backend/src/aurinko/updater/types.gleam @@ -0,0 +1,23 @@ +import ap_systems/api +import ap_systems/module_power +import birl +import gleam/dict +import gleamy/red_black_tree_set.{type Set} + +pub type ProductionInfo = + api.ProductionInfo + +pub type MomentaryPower = + module_power.MomentaryPower + +pub type ModulePower { + ModulePower(id: String, power: dict.Dict(birl.Time, MomentaryPower)) +} + +pub type ModuleDataset { + ModuleDataset(times: Set(birl.Time), datas: dict.Dict(String, ModulePower)) +} + +pub type Dataset { + Dataset(production_info: ProductionInfo, modules: ModuleDataset) +} diff --git a/backend/src/aurinko/web.gleam b/backend/src/aurinko/web.gleam index 6fb37bf..0762feb 100644 --- a/backend/src/aurinko/web.gleam +++ b/backend/src/aurinko/web.gleam @@ -1,8 +1,13 @@ +import aurinko/updater/pubsub import aurinko/web/router import mist import wisp -pub fn init(port: Int, secret_key_base: String) { +pub fn init( + port: Int, + secret_key_base: String, + _updater_pubsub: pubsub.UpdaterPubSub, +) { wisp.configure_logger() let assert Ok(_) = diff --git a/backend/src/aurinko_backend.gleam b/backend/src/aurinko_backend.gleam new file mode 100644 index 0000000..8eb6a13 --- /dev/null +++ b/backend/src/aurinko_backend.gleam @@ -0,0 +1,51 @@ +import ap_systems/api +import ap_systems/module_power +import aurinko/updater +import aurinko/updater/pubsub +import aurinko/web +import dot_env +import dot_env/env +import gleam/erlang/process +import gleam/int +import gleam/io +import gleam/result +import gleam/string +import glubsub + +pub fn main() { + dot_env.new_with_path("./.env") + |> dot_env.set_ignore_missing_file(True) + |> dot_env.load() + + let assert Ok(username) = env.get("USERNAME") + let assert Ok(password) = env.get("PASSWORD") + let assert Ok(port) = env.get("PORT") + let assert Ok(port) = int.parse(port) + let assert Ok(secret_key_base) = env.get("SECRET_KEY_BASE") + + let assert Ok(updater_pubsub) = pubsub.init() + let assert Ok(subject) = updater.start(username, password, updater_pubsub) + let assert Ok(_) = web.init(port, secret_key_base, updater_pubsub) + io.debug(subject) + process.sleep(15_000) + io.debug(updater.get_data(subject)) + process.sleep_forever() + // use cookies <- result.try( + // api.login(username, password) |> result.map_error(string.inspect), + // ) + + // io.debug(cookies) + // use prod <- result.try( + // api.get_production(cookies) |> result.map_error(string.inspect), + // ) + // io.debug(prod) + + // use ids <- result.try( + // io.debug(api.get_module_ids(cookies)) |> result.map_error(string.inspect), + // ) + // use module_data <- result.try( + // api.get_module_power(cookies, ids) |> result.map_error(string.inspect), + // ) + // io.debug(module_power.decode_api_data(module_data)) + // |> result.map_error(string.inspect) +}