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?
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 ""))))
They way you’re calling your every
function is wrong, I believe. map
returns a sequence (one argument).
Try changing every
to take one argument. (I haven’t tested this suggestion, just from skimming the code, could be wrong).
That was it! Thank you.
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
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;
Second option definitely adds complexity but it is more robust and will give you greater power and flexibility;
yes, recommend using an atom to save sensor state. or some number of previous states
Hey, I would like to understand why I have a certain behavior with atoms and promises. Consider this snippet:
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
and same for the other cases. Am I missing some obvious scoping issue here?
What's the context of that code?
It's in a function that passes down props to a react component
:cy
is the callback with which the component refers to itself, and the .on
fields are promises on certain events
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
.
wait, but shouldn't :cy be called only once?
at startup? That might very well be my misunderstanding!
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.
Just add a logging statement there and check. :)
very good idea, thank you!
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?
No.
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 [_] ...))
.
Again, I have no idea how cytoscape
works, so that might not be the case at all.
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?
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??)
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:
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
.
Ok thank you so much. I will give it a try 😊
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
.And by "it" I meant specifically "I seem to get a different copy of the library".
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.
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.
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.
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!
(I had built a bunch of cljsjs packages before discovering shadow)
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. :)
I'm ready to become a cool kid!!
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
Maybe there is some way to include an NPM dependency note or something in the published jar now?
Or alternately are folks publishing cljs libraries to NPM to get around this?
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.
Ah yes, there's a documentation section about that: https://shadow-cljs.github.io/docs/UsersGuide.html#publish-deps-cljs
@p-himik 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…
wait, progress… error seems tohave resolved…
Good to hear. :) But for any shadow-cljs questions, it's better to ask in #shadow-cljs directly - I'm not an expect here.