Fork me on GitHub
#clojurescript
<
2021-05-02
>
Carl01:05:45

Hi all, I’m a beginner doing some home automation stuff with cljs and some npm Javascript libraries for hue and tradfri. What’s the appropriate way to have a function on a regular timer that reads a sensor and does something based on the last sensor read and this sensor read? Is there a sensible way to pass the last sensor state to the next function call as an argument? Or is this where your use an atom within the closure instead of it being an argument to the function?

raspasov01:05:17

If you need something basic that doesn’t care about millisecond accuracy, js/setInterval works just fine https://www.w3schools.com/jsref/met_win_setinterval.asp

raspasov02:05:48

If you want something more precise, and for longer intervals/periods (e.g. tomorrow at 9AM, tonight at midnight, or every Monday at 7AM), I recommend a library like https://github.com/juxt/tick and using the values that the library provides to “trigger” when your automation functions will run;

raspasov02:05:00

Second option definitely adds complexity but it is more robust and will give you greater power and flexibility;

sova-soars-the-sora02:05:51

yes, recommend using an atom to save sensor state. or some number of previous states

Carl12:05:45

I ended up using setInterval for now and that's working well! I ran into an issue though. I have a collection of lights which are js objects. Within a go block I call my "(switch-light+ [light on?])" function on each light which calls a method on a light which returns a promise. When I do the following: (<p! (every (map #(switch-light+ % true) kitchen-lights)))) it's as if the switch-light function doesn't get called at all. However, getting the nth element out of the collection of lights and passing it to switch-light does work. Any thoughts on why? I've pasted a extract of the code below with the bulk of the logic omitted

(defn switch-light+ [^js light on?]
  (println "-- switch-lights")
  (. light (switch on?)))

(defn every [& args]
  (js/Promise.all (into-array args)))

(defn update-kitchen-lights [^js presence-sensor ^js kitchen-lights]
  (go
    (let [is-present? (. presence-sensor -presence)
          some-lights-on? (some #(. ^js % -isOn) kitchen-lights)
          someone-entered? (and is-present?
                                (not (:was-present @kitchen-state)))
          timed-out? (and (not is-present?)
                          (> (:timeout @kitchen-state) 5))]

     ...

      ;; Someone just entered the room => lights on
      (when (and someone-entered?
                 (not some-lights-on?))
        ;; Lights on (ToDo: Wait for this and check response)
        (println "Turning lights on")

        ;; LINE THAT DOES NOT WORK
        (<p! (every (map #(switch-light+ % true) kitchen-lights))))

        ;; LINES THAT DO WORK
        (<p! (switch-light+ (nth kitchen-lights 0) true))
        (<p! (switch-light+ (nth kitchen-lights 1) true))
        (<p! (switch-light+ (nth kitchen-lights 2) true))
        (<p! (switch-light+ (nth kitchen-lights 3) true)))

      ...


      (swap! kitchen-state assoc :was-present is-present?)
      (swap! kitchen-state update :seconds + 1)
      (println "kitchen-state-after: " @kitchen-state)
      (println ""))))

raspasov13:05:20

They way you’re calling your every function is wrong, I believe. map returns a sequence (one argument).

raspasov13:05:00

Try changing every to take one argument. (I haven’t tested this suggestion, just from skimming the code, could be wrong).

Carl12:05:14

That was it! Thank you.

👍 2
Carlo12:05:32

Hey, I would like to understand why I have a certain behavior with atoms and promises. Consider this snippet:

Carlo12:05:51

If I do an action that triggers the add case, I would expect to memorize the layout in the current-layout atom, but in subsequent interactions, it always comes back null

Carlo12:05:38

and same for the other cases. Am I missing some obvious scoping issue here?

p-himik12:05:12

What's the context of that code?

Carlo12:05:19

It's in a function that passes down props to a react component

Carlo12:05:10

:cy is the callback with which the component refers to itself, and the .on fields are promises on certain events

p-himik12:05:11

On each call of :cy, the atom is re-created. It's not stored anywhere. This is Reagent, right? You need a form-2 component or reagent.core/with-let.

Carlo12:05:41

wait, but shouldn't :cy be called only once?

Carlo12:05:07

at startup? That might very well be my misunderstanding!

p-himik12:05:27

I have no idea how cytoscape works. It's easy to imagine that it will be called again when @app-state/app-state changes or the parent component is re-rendered.

p-himik12:05:40

Just add a logging statement there and check. :)

Carlo12:05:52

very good idea, thank you!

Carlo12:05:47

now, I can get that working with a with-let block that wraps all the function, but I really would like to just wrap the content of :cy, so that I can initialize the atom with an initial layout instead of null. Would there be a way to do that?

p-himik13:05:15

Wait, but you don't need the atom at all, do you? Seems like you should be able to call (.on cy "drag" ...) right inside that (.on cy "add" (fn [_] ...)).

p-himik13:05:31

Again, I have no idea how cytoscape works, so that might not be the case at all.

Sam Ritchie14:05:06

Hey all! I’ve got a JS interop issue that is stumping me. The #sicmutils library has a numeric tower built on the “Fraction” type from Fraction.js, which I require with ["fraction.js/bigfraction.js" :as Fraction] (in https://github.com/sicmutils/sicmutils/blob/master/src/sicmutils/ratio.cljc). My issue is that if I use the same require form in a different namespace, then I seem to get a different copy of the library. I extend a bunch of protocols in ratio.cljc, and if I make an instance with (Fraction. 1 2) in a different namespace, it’s a different type. More simply, (instance? Fraction x) fails in namespace b if x was created with the Fraction constructor in namespace a , and I can’t type-hint instances from a in any other namespace. Is there some way to do the require above in a way that will work across multiple namespaces?

Sam Ritchie14:05:31

All I REALLY need is a way to (instance? Fraction x) in two separate namespaces, where b depends on a. I would just put (def ratiotype Fraction) in namespace a… but the problem is that b needs to type hint function arguments with Fraction, and I don’t THINK I can do ^ratiotype as a type hint (right??)

p-himik14:05:09

I guess it depends on your build setup. I cannot reproduce it with shadow-cljs:

;; app/a.cljs
(ns app.a
  (:require ["fraction.js" :as Fraction]))

(def fraction Fraction)

;; app/b.cljs
(ns app.b
  (:require ["fraction.js" :as Fraction]))

(def fraction Fraction)

;; app/core.cljs
(ns app.core
  (:require [app.a :as a]
            [app.b :as b]))

(defn ^:export main []
  (js/console.log (= a/fraction b/fraction)))
The code above outputs true.

p-himik14:05:52

And by "it" I meant specifically "I seem to get a different copy of the library".

p-himik14:05:55

You're talking about type hints, but type hints don't work in ClojureScript. The only thing that they do is to enable externs autoinference. The actual types are never checked.

p-himik14:05:50

A common practice to enable such autoinference is to just put ^js in front of the relevant symbol - that's enough. But in your case, I think it's a built problem and not an actual type problem.

p-himik14:05:33

I took a deeper look at how sicmutils is structured, and to me it doesn't make much sense, to be honest. You're using both lein-cljsbuild and shadow-cljs, but the latter is only used for testing, it seems. You're using both fraction.js via npm and cljsjs/bigfraction - that might be the culprit. I would just switch to a single build tool for everything. And I would completely remove any cljsjs packages and replace them with their NPM originals. Regardless of whether it actually fixes the problem or not.

Sam Ritchie15:05:41

Okay, excellent, that is almost certainly it. Test running was tough enough to set up last year that I’ve avoided migrating off of “doo”, my old test runner, and moved to shadow for detecting missing hints etc... but you’re right that getting shadow to do everything cljs related is the right move. I’ll set some time aside here. Thank you!

👍 3
Sam Ritchie15:05:16

(I had built a bunch of cljsjs packages before discovering shadow)

p-himik15:05:22

AFAIK you can use NPM packages [almost] just as well in regular CLJS, without shadow-cljs. But I might be wrong since I only use shadow-cljs. :)

Sam Ritchie15:05:46

I'm ready to become a cool kid!!

Sam Ritchie15:05:16

One issue I remember with shadow as my main tool was that if I migrate off cljsjs, any consumer has to know and specifically include the fraction NPM dep vs getting it transitively

Sam Ritchie15:05:45

Maybe there is some way to include an NPM dependency note or something in the published jar now?

Sam Ritchie15:05:03

Or alternately are folks publishing cljs libraries to NPM to get around this?

p-himik15:05:20

I've never made any libraries, but FWIW I would: - Put regular CLJS sources out there with deps.edn, so that they could be used with Git SHA coordinates in other tools.deps projects - Remove all traces of Leiningen - Build and publish a jar with just the sources - Make a note in the README describing how to use this library, including installing the NPM packages But I have no clue how that would work if e.g. you use shadow-cljs-specific :require. It probably wouldn't so you'd have to find a common ground between shadow-cljs imports and regular CLJS imports.

p-himik15:05:15

Ah yes, there's a documentation section about that: https://shadow-cljs.github.io/docs/UsersGuide.html#publish-deps-cljs

❤️ 3
Sam Ritchie20:05:46

@U2FRKM4TW unfortunately I’m finding it deeply confusing, getting a node REPL setup going with shadow-cljs… this is what had blocked me before! If you have any advice…

Sam Ritchie20:05:53

wait, progress… error seems tohave resolved…

p-himik21:05:05

Good to hear. :) But for any shadow-cljs questions, it's better to ask in #shadow-cljs directly - I'm not an expect here.

Sophie14:05:12

Hey Guys, is there anybody who can tell me why this is working: (defn rooms-size [value title] [:div [:input {:type "text" :value (:Size @value) :on-change #(swap! value assoc :Size (.. % -target -value)) :on-mouse-leave #(rf/dispatch [:change-room-size [title (int (.. % -target -value))]])} ]] ) and this not (let [t-room-size (atom {:qm (get-in @liste [:rooms @(rf/subscribe [:room-flag]) :qm])})] [:div [:input {:type "text" :value (:qm @t-room-size) :on-change #(swap! t-room-size assoc :qm (.. % -target -value)))} ]]) I can't change the value of the input field in the second version:face_with_rolling_eyes:

p-himik14:05:02

It's probably more apt for #reagent Two reasons: - Regular built-in atom doesn't trigger Reagent re-renders. Only reagent.core/atom does, so you have to use that - You re-create the atom on each re-render. You have to use a form-2 component or reagent.core/with-let.

Sophie14:05:43

Ok thank you so much. I will give it a try 😊

👍 3