Breandan Dezendorf 2 жил өмнө
parent
commit
b1abde56d4

+ 21 - 0
astoria-hc/.gitignore

@@ -0,0 +1,21 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+build
+
+# Dependency directories (remove the comment below to include it)
+vendor/
+
+espressod*
+
+Astoria*

+ 0 - 0
astoria-hc/BUILD


+ 339 - 0
astoria-hc/LICENSE

@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

+ 73 - 0
astoria-hc/Makefile

@@ -0,0 +1,73 @@
+HOST_USER := $(shell whoami)
+.PHONY: build
+
+build:
+	mkdir -p build
+
+	docker run \
+		--rm \
+		-e HOST_USER=$(HOST_USER) \
+		-v $(PWD):/build \
+		-w /build \
+		golang \
+		bash -c 'go mod vendor && go build -mod=vendor -o espressod cmd/main.go'
+	docker run \
+		--rm \
+		-e HOST_USER=$(HOST_USER) \
+		-e GOOS=linux \
+		-e GOARCH=arm \
+		-e GOARM=5 \
+                -v $(PWD):/build \
+                -w /build \
+                golang \
+                bash -c 'go mod vendor && go build -mod=vendor -o espressod_armv5 cmd/main.go'
+
+check: go_fmt go_lint go_vet
+
+go_fmt:
+	docker run \
+		--rm \
+		-v $(PWD):/installer \
+		-w /installer \
+		golang \
+		bash -c "find . -path ./vendor -prune -o -name '*.go' -exec gofmt -l {} \; | tee fmt.out && if [ -s fmt.out ] ; then exit 1; fi "
+
+
+go_vet:
+	docker run\
+		--rm \
+		-v $(PWD):/installer \
+		-w /installer \
+		golang \
+		bash -c "go vet ./..."
+
+go_lint:
+	docker run \
+		--rm \
+		-v $(PWD):/installer \
+		-w /installer \
+		golang \
+		bash -c 'go get golang.org/x/lint/golint && go list ./... | xargs -L1 golint -set_exit_status'
+
+run:
+	docker run \
+		--rm \
+		-v $(PWD):/build \
+		-w /build \
+		golang \
+		bash -c 'go run .'
+		
+
+docker:
+	docker build -t astoria-hc:latest . < Dockerfile
+
+deploy:
+	scp espressod_armv5 pi@astoria:espressod_armv5
+
+
+clean:
+	rm -f main
+	rm -f espressod
+	rm -f espressod_armv5
+	rm -rf build
+

+ 33 - 0
astoria-hc/README.md

@@ -0,0 +1,33 @@
+# espressod
+Espresso Machine Control - HomeKit and Promtheus enabled
+
+```
+Usage of ./espressod:
+  -db string
+    	Database path (default "./db")
+  -gpio
+    	load gpio code (default true)
+  -maxTemp float
+    	Maximum temperature value (default 130)
+  -metrics
+    	Enable prometheus metrics (default true)
+  -minTemp float
+    	Minimum temperature value (default 10)
+  -name string
+    	Device name (default "Astoria Boiler Thermostat")
+  -pin string
+    	Homekit Pairing PIN (default "00102003")
+  -promPort int
+    	Port to reigster /metrics handler on (default 2112)
+  -stepTemp float
+    	Temperature setting step size (default 0.1)
+```
+
+# How to Use
+`espressod` is a standalone golang daemon that creates a Thermostat accessory for use in HomeKit. It also exposes most of the metrics relevant to the operation of the system via a prometheus instrumentation URI. Starting the daemon with no options will setup the accessory and try to control the boiler via a solid state relay. A momentary switch changes the setpoint from min to max and back.
+
+# Why I Wrote This
+
+I purchased a second hand commerical espresso machine a few years ago, and it is far too much work to turn it on and then wait the 30+ minutes for it to warm up fully. I was also concerend with how much power it was drawing. So I in-lined a 40A 250V Solid State Relay which I hooked to a raspberry pi, and wired a PT100 temperature sensor into the boiler. I now have very precise and very easy to automate temperature control of the machine.
+
+Some details can be found on [The Guru College](http://gurucollege.net/post/astoria-argenta-sae-project/).

+ 33 - 0
astoria-hc/cmd/BUILD.bazel

@@ -0,0 +1,33 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+    name = "lib",
+    srcs = ["main.go"],
+    importpath = "",
+    visibility = ["//visibility:private"],
+    deps = [
+        "@com_github_brutella_hc//:go_default_library",
+        "@com_github_brutella_hc//accessory:go_default_library",
+        "@com_github_bwdezend_astoria_hc//internal/core:go_default_library",
+        "@com_github_bwdezend_astoria_hc//internal/telemetry:go_default_library",
+    ],
+)
+
+go_binary(
+    name = "cmd",
+    embed = [":cmd_lib"],
+    visibility = ["//visibility:public"],
+)
+
+go_library(
+    name = "cmd_lib",
+    srcs = ["main.go"],
+    importpath = "github.com/example/project/astoria-hc/cmd",
+    visibility = ["//visibility:private"],
+    deps = [
+        "//astoria-hc/vendor/github.com/brutella/hc",
+        "//astoria-hc/vendor/github.com/brutella/hc/accessory",
+        "@com_github_bwdezend_astoria_hc//internal/core:go_default_library",
+        "@com_github_bwdezend_astoria_hc//internal/telemetry:go_default_library",
+    ],
+)

+ 92 - 0
astoria-hc/cmd/main.go

@@ -0,0 +1,92 @@
+package main
+
+import (
+	"flag"
+	"log"
+	"os"
+	"os/signal"
+	"syscall"
+
+	"github.com/brutella/hc"
+	"github.com/brutella/hc/accessory"
+	"github.com/bwdezend/astoria-hc/internal/core"
+	"github.com/bwdezend/astoria-hc/internal/telemetry"
+)
+
+var acc accessory.Thermostat
+var deviceName = flag.String("name", "Astoria Boiler Thermostat", "Device name")
+var dbPath = flag.String("db", "./db", "Database path")
+var enableMetics = flag.Bool("metrics", true, "Enable prometheus metrics")
+var prometheusPort = flag.Int("promPort", 2112, "Port to reigster /metrics handler on")
+var temperatureMinimim = flag.Float64("minTemp", 10.0, "Minimum temperature value")
+var temperatureMaximum = flag.Float64("maxTemp", 130.0, "Maximum temperature value")
+var temperatureStepSize = flag.Float64("stepTemp", 0.1, "Temperature setting step size")
+var homekitPin = flag.String("pin", "00102003", "Homekit Pairing PIN")
+
+var gpio = flag.Bool("gpio", true, "load gpio code")
+
+func init() {
+	flag.Parse()
+	info := accessory.Info{
+		Name: *deviceName,
+	}
+
+	acc = *accessory.NewThermostat(info, 10.0, *temperatureMinimim, *temperatureMaximum, *temperatureStepSize)
+}
+
+func check(e error) {
+	if e != nil {
+		panic(e)
+	}
+}
+
+var p = 3.0
+
+func main() {
+	c := make(chan os.Signal)
+	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
+	go func() {
+		<-c
+		os.Exit(0)
+	}()
+
+	config := hc.Config{Pin: *homekitPin}
+	t, err := hc.NewIPTransport(config, acc.Accessory)
+	check(err)
+	hc.OnTermination(func() {
+		t.Stop()
+	})
+
+	if *enableMetics {
+		go telemetry.PrometheusMetrics(*prometheusPort)
+	}
+
+	// Recover previously set temperature on program start
+	core.SetTargetTemp(acc, core.RecoverTemp())
+
+	// Picks up the current temperature every second from *path/current_temp
+	go core.GetCurrentTemp(acc)
+
+	// Run the PID loop itself
+	go core.TemperatureProportional(acc, *gpio)
+
+	// Enable the "boiler isn't heating" error handling
+	go core.TemperatureErrorDetection(acc)
+
+	// Runs the REST-y interface for adjusting the setpoint and causing the program to exit.
+	//go telemetry.SetpointHandler()
+
+	// If the -gpio flag is set to true load the Raspberry Pi gpio handling code.
+	if *gpio {
+		log.Println("rpi-gpio enabled - enabling power button on pin 26")
+		go core.PowerButton(acc)
+	}
+
+	// Enable the homekit code to change the setpoint
+	acc.Thermostat.TargetTemperature.OnValueRemoteUpdate(func(temp float64) {
+		core.SetTargetTemp(acc, temp)
+	})
+
+	t.Start()
+
+}

+ 34 - 0
astoria-hc/design.md

@@ -0,0 +1,34 @@
+The system needs to do the following things:
+
+    operate a PID loop that:
+        reads from the temperature sensor
+        writes to the duty cycle
+        stores the error etc for PID
+
+    make available to homekit:
+        read the current temperature
+        write the desired temperature
+        read the off/heating/cooling status
+        write the off/heating/cooling status
+
+    make available to web interface:
+        read the current temperature
+        write the desired temperature
+        read the off/heating/cooling status
+        write the off/heating/cooling status
+
+    make available to promtheus interface:
+        current temperature
+        current heating/cooling
+        errors with things
+        metrics
+
+    core logic:
+        read/write the setpoint
+        read/write available the setpoint
+        read/write available the current temp
+        read/write available the state of off/heating/cooling
+    
+
+    read the current temperature from the MAX318650
+    operate a PID loop that con

+ 13 - 0
astoria-hc/go.mod

@@ -0,0 +1,13 @@
+module github.com/bwdezend/astoria-hc
+
+go 1.13
+
+require (
+	github.com/brutella/hc v1.2.1
+	github.com/gorilla/mux v1.7.4 // indirect
+	github.com/prometheus/client_golang v1.5.1
+	github.com/stianeikeland/go-rpio v4.2.0+incompatible
+	github.com/vemo-france/max31865 v0.0.0-20190220090227-8f9a7d8ee8f3
+	golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
+	periph.io/x/periph v3.6.4+incompatible // indirect
+)

+ 142 - 0
astoria-hc/go.sum

@@ -0,0 +1,142 @@
+github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
+github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/brutella/dnssd v1.1.1 h1:Ar5ytE2Z9x5DTmuNnASlMTBpcQWQLm9ceHb326s0ykg=
+github.com/brutella/dnssd v1.1.1/go.mod h1:9gIcMKQSJvYlO2x+HR50cqqjghb9IWK9hvykmyveVVs=
+github.com/brutella/hc v1.2.1 h1:YkWfHtfeU51nrjd3yVtHeoBxF5ADsQA7k9yqeD4hFgk=
+github.com/brutella/hc v1.2.1/go.mod h1:klfQdOw5Er04/4xuDyv9Y776MWzObiufjP4Hjz/cTDc=
+github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
+github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0=
+github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA=
+github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/stianeikeland/go-rpio v4.2.0+incompatible h1:CUOlIxdJdT+H1obJPsmg8byu7jMSECLfAN9zynm5QGo=
+github.com/stianeikeland/go-rpio v4.2.0+incompatible/go.mod h1:Sh81rdJwD96E2wja2Gd7rrKM+XZ9LrwvN2w4IXrqLR8=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/tadglines/go-pkgs v0.0.0-20140924210655-1f86682992f1 h1:ms/IQpkxq+t7hWpgKqCE5KjAUQWC24mqBrnL566SWgE=
+github.com/tadglines/go-pkgs v0.0.0-20140924210655-1f86682992f1/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE=
+github.com/vemo-france/max31865 v0.0.0-20190220090227-8f9a7d8ee8f3 h1:fm88ZFc0za9NFphTs+iQVhzqGopyBpWCu/14bVtud5o=
+github.com/vemo-france/max31865 v0.0.0-20190220090227-8f9a7d8ee8f3/go.mod h1:StvcmtiJwuJ3vY/flU7rJStTdPCODs/40R9S0t/1HgY=
+github.com/xiam/to v0.0.0-20191116183551-8328998fc0ed h1:Gjnw8buhv4V8qXaHtAWPnKXNpCNx62heQpjO8lOY0/M=
+github.com/xiam/to v0.0.0-20191116183551-8328998fc0ed/go.mod h1:cqbG7phSzrbdg3aj+Kn63bpVruzwDZi58CpxlZkjwzw=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+periph.io/x/periph v3.6.4+incompatible h1:8FyXTbu9lcMVofz8mf+cj1pzTLN4V6EuPY2EF+DoJF4=
+periph.io/x/periph v3.6.4+incompatible/go.mod h1:EWr+FCIU2dBWz5/wSWeiIUJTriYv9v2j2ENBmgYyy7Y=

+ 18 - 0
astoria-hc/internal/core/BUILD.bazel

@@ -0,0 +1,18 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "core",
+    srcs = [
+        "app.go",
+        "gpio.go",
+        "persist.go",
+    ],
+    importpath = "github.com/example/project/astoria-hc/internal/core",
+    visibility = ["//astoria-hc:__subpackages__"],
+    deps = [
+        "//astoria-hc/vendor/github.com/brutella/hc/accessory",
+        "//astoria-hc/vendor/github.com/stianeikeland/go-rpio",
+        "//astoria-hc/vendor/github.com/vemo-france/max31865",
+        "@com_github_bwdezend_astoria_hc//internal/telemetry:go_default_library",
+    ],
+)

+ 180 - 0
astoria-hc/internal/core/app.go

@@ -0,0 +1,180 @@
+package core
+
+import (
+	"log"
+	"time"
+
+	"github.com/brutella/hc/accessory"
+	"github.com/bwdezend/astoria-hc/internal/telemetry"
+	"github.com/vemo-france/max31865"
+)
+
+var p = 3.0
+
+// GetCurrentTemp read from the MAX31865 to read
+// the current state of the steam side of the boiler temperature
+func GetCurrentTemp(acc accessory.Thermostat) {
+	if err := max31865.Init(); err != nil {
+		log.Fatalf("initialization failed : %s", err)
+	}
+	sensor := max31865.Create("8", "9", "10", "11")
+	var boilerTemperature float64
+	for {
+		boilerTemperature = float64(sensor.ReadTemperature(100, 430))
+		if boilerTemperature < 1.0 {
+			telemetry.SensorFaultCount.Inc()
+			log.Println("Sensor fault")
+		} else {
+			acc.Thermostat.CurrentTemperature.SetValue(boilerTemperature)
+			telemetry.CurrentTemperature.Set(boilerTemperature)
+		}
+		time.Sleep(500 * time.Millisecond)
+	}
+}
+
+// SetTargetTemp adjusts the setpoint for the PID loop and updates the homekit interfaces
+func SetTargetTemp(acc accessory.Thermostat, setTemp float64) {
+	if setTemp > 124.0 {
+		setTemp = 124.0
+	}
+
+	log.Printf("setting setpoint to %.2f", setTemp)
+	PersistTemp(setTemp)
+
+	acc.Thermostat.TargetTemperature.SetValue(setTemp)
+	telemetry.SetpointTemperature.Set(setTemp)
+}
+
+// HeaterWindow doc Take two inputs - the duration of the cycle and the proportion of the cycle
+// and turn the heating element on for that percentage of the cycle. If the
+// windowSize is 15.0 and the enabledTime is 0.7, this turns the heating element
+// on for 10.5 seconds and off for 4.5 seconds before returning
+func HeaterWindow(windowSize float64, enabledTime float64, gpioEnabled bool) {
+	var disabledTime float64 = 0
+	enabledTime = windowSize * enabledTime * 1000
+	disabledTime = windowSize*1000 - enabledTime
+
+	if enabledTime > 1000.0 {
+		enabledTime = 1000.0
+	}
+	if enabledTime > 0 {
+		if gpioEnabled {
+			HeaterControl(true)
+		}
+		time.Sleep(time.Duration(enabledTime) * time.Millisecond)
+		telemetry.SecondsActive.Add(enabledTime / 1000)
+	}
+	if gpioEnabled {
+		HeaterControl(false)
+	}
+	time.Sleep(time.Duration(disabledTime) * time.Millisecond)
+}
+
+// TemperatureProportional is a dead simple proportional control loop. Take the difference in setpoint
+// and current temp, multiply by the gain, and use the result to control
+// the duty cycle on the boiler, represented as a float between 0.0 and 1.0
+func TemperatureProportional(acc accessory.Thermostat, gpioEnabled bool) {
+
+	for {
+		current := 0.0
+		if gpioEnabled {
+			current = acc.Thermostat.CurrentTemperature.GetValue()
+		}
+		setpoint := acc.Thermostat.TargetTemperature.GetValue()
+		error := (setpoint - current) * p * 0.1
+
+		if error > 1.0 {
+			error = 1.0
+		}
+
+		if error < 0.01 {
+			error = 0.0
+		}
+
+		// log.Printf("Duty Cycle: %.2f, Current: %.2f, Setpoint: %.2f\n", error, current, setpoint)
+
+		HeaterWindow(1.0, error, gpioEnabled)
+
+	}
+}
+
+// TemperatureErrorDetection is a error handling function to turn the SSR off if the boiler doesn't
+// appear to be warming as a result of input. The most common reason for this
+// is that the power switch on the machine is turned off, but the timer still
+// fired. Less common reasons would be a heater element malfunction, a boiler
+// rupture, or a failure of the temperature sensor itself.
+func TemperatureErrorDetection(acc accessory.Thermostat) {
+
+	lastTemp := 0.0
+	countdown := 2
+	for {
+		setpoint := acc.Thermostat.TargetTemperature.GetValue()
+		time.Sleep(10 * time.Second)
+
+		current := acc.Thermostat.CurrentTemperature.GetValue()
+		error := (setpoint - current)
+
+		if error > 5.0 {
+			if lastTemp >= current {
+				if countdown > 0 {
+					log.Printf("Warning: boiler does not appear to be heating (%.2f gt %.2f). Countdown: %d", lastTemp, current, countdown)
+					countdown--
+				} else if countdown == 0 {
+					log.Printf("Error: boiler does not appear to be heating (%.2f gt %.2f). Changing setpoint to 0.0", lastTemp, current)
+					SetTargetTemp(acc, 0.0)
+					countdown = 2
+				}
+			}
+		} else if countdown < 2 {
+			log.Printf("Warning: boiler does not appear to be heating (%.2f gt %.2f). Countdown: %d", lastTemp, current, countdown)
+			countdown = 2
+		}
+		lastTemp = current
+	}
+}
+
+// Re-evaluate if we want to be able to set the temp from a REST endpoint
+// type updateVars struct {
+// 	Temperature float64 `json:"temperature,omitempty"`
+// 	Proportion  float64 `json:"proportion,omitempty"`
+// }
+//
+// // UpdateSettings doc
+// func UpdateSettings(w http.ResponseWriter, r *http.Request) {
+// 	var update updateVars
+// 	if r.Method == "POST" {
+// 		body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
+// 		if err != nil {
+// 			http.Error(w, "Error reading request body",
+// 				http.StatusInternalServerError)
+// 		}
+// 		if err := r.Body.Close(); err != nil {
+// 			panic(err)
+// 		}
+//
+// 		if err := json.Unmarshal(body, &update); err != nil {
+// 			w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+// 			w.WriteHeader(422) // unprocessable entity
+// 			if err := json.NewEncoder(w).Encode(err); err != nil {
+// 				panic(err)
+// 			}
+// 		}
+// 		if update.Temperature != 0 {
+// 			SetTargetTemp(update.Temperature)
+// 		}
+// 		if update.Proportion != 0 {
+// 			log.Printf("updating p constant to %.2f\n", update.Proportion)
+// 			p = update.Proportion
+// 		}
+//
+// 	} else {
+// 		http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
+// 	}
+// }
+//
+// // SetpointHandler handles
+// func SetpointHandler() {
+// 	router := mux.NewRouter().StrictSlash(true)
+// 	router.HandleFunc("/update", UpdateSettings)
+// 	log.Fatal(http.ListenAndServe(":2113", router))
+// }

+ 53 - 0
astoria-hc/internal/core/gpio.go

@@ -0,0 +1,53 @@
+package core
+
+import (
+	"log"
+	"time"
+
+	"github.com/brutella/hc/accessory"
+	"github.com/bwdezend/astoria-hc/internal/telemetry"
+	"github.com/stianeikeland/go-rpio"
+)
+
+func init() {
+	rpio.Open()
+}
+
+// PowerButton sets up the button hooked to pin 26
+// on the Rasberry Pi to act as a high/low control
+// for the system.
+func PowerButton(acc accessory.Thermostat) {
+	high := 124.0
+	low := 30.0
+
+	button := rpio.Pin(26)
+	button.Input()
+	button.PullUp()
+
+	for {
+		if button.Read() == 0 {
+			log.Println("button pressed")
+			if acc.Thermostat.TargetTemperature.GetValue() >= (high - 10.0) {
+				SetTargetTemp(acc, low)
+			} else {
+				SetTargetTemp(acc, high)
+			}
+			time.Sleep(500 * time.Millisecond)
+		}
+		time.Sleep(50 * time.Millisecond)
+	}
+}
+
+// HeaterControl turns on and off voltate to the
+// pin hooked to the Solid State Relay.
+func HeaterControl(on bool) {
+	pin := rpio.Pin(14)
+	pin.Output()
+
+	telemetry.RelayActivations.Inc()
+	if on {
+		pin.High()
+	} else {
+		pin.Low()
+	}
+}

+ 16 - 0
astoria-hc/internal/core/homekit.go.bak

@@ -0,0 +1,16 @@
+package homekit
+
+import (
+	"flag"
+
+	"github.com/brutella/hc/accessory"
+)
+
+func init() {
+	flag.Parse()
+	info := accessory.Info{
+		Name: *deviceName,
+	}
+
+	acc = *accessory.NewThermostat(info, 10.0, *temperatureMinimim, *temperatureMaximum, *temperatureStepSize)
+}

+ 52 - 0
astoria-hc/internal/core/persist.go

@@ -0,0 +1,52 @@
+package core
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"strconv"
+)
+
+func init() {
+
+}
+
+
+// PersistTemp creates a file in /dev/shm
+// and saves the setppoint temperature to it. Every time
+// the setupoint is changed, this function should be called
+// and will persist the value. This is not persistant across
+// system reboots.
+func PersistTemp(currentTemp float64) {
+	persistFile := "/dev/shm/persistFile"
+	f, err := os.OpenFile(persistFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
+	if err != nil {
+		log.Fatalf("persistence file write failed : %s", err)
+	}
+	defer f.Close()
+	fmt.Fprintf(f, "%f", currentTemp)
+	f.Sync()
+}
+
+// RecoverTemp is a companion to PersistTemp,
+// reading from the file in /dev/shm and setting the setpoint
+// at application startup.
+func RecoverTemp() (recoverTemp float64) {
+	recoverFile := "/dev/shm/persistFile"
+
+	log.Printf("reading setpoint from %s", recoverFile)
+
+	f, err := os.ReadFile(recoverFile)
+	if err != nil {
+		log.Fatalf("persistence file recovery failed: %s", err)
+	}
+
+	recoverTemp, err = strconv.ParseFloat(string(f), 64)
+	if err != nil {
+		log.Fatalf("persistence conversion failed: %s", err)
+	}
+
+	log.Printf("setting setpoint to %f", recoverTemp)
+	return recoverTemp
+
+}

+ 19 - 0
astoria-hc/internal/core/web.go.bak

@@ -0,0 +1,19 @@
+package core
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/brutella/hc/accessory"
+)
+
+func powerToggle(w http.ResponseWriter, r *http.Request) {
+	TogglePower()
+	//os.Exit(0)
+}
+
+// WebInterface handles the prometheus scrape endpoint
+func WebInterface(acc accessory.Thermostat) {
+	http.HandleFunc("/power", powerToggle(acc))
+	http.ListenAndServe(fmt.Sprintf(":%d", 8192), nil)
+}

+ 13 - 0
astoria-hc/internal/telemetry/BUILD.bazel

@@ -0,0 +1,13 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "telemetry",
+    srcs = ["prom.go"],
+    importpath = "github.com/example/project/astoria-hc/internal/telemetry",
+    visibility = ["//astoria-hc:__subpackages__"],
+    deps = [
+        "//astoria-hc/vendor/github.com/prometheus/client_golang/prometheus",
+        "//astoria-hc/vendor/github.com/prometheus/client_golang/prometheus/promauto",
+        "//astoria-hc/vendor/github.com/prometheus/client_golang/prometheus/promhttp",
+    ],
+)

+ 55 - 0
astoria-hc/internal/telemetry/prom.go

@@ -0,0 +1,55 @@
+package telemetry
+
+import (
+	"fmt"
+	"net/http"
+	"os"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
+)
+
+var (
+	// SecondsActive is the number of seconds, since program start
+	//  that the heating elemnent has been active.
+	SecondsActive = promauto.NewCounter(prometheus.CounterOpts{
+		Name: "heater_element_active_seconds",
+		Help: "The total number of seconds the heating element has been active",
+	})
+
+	// RelayActivations counts the number of times the relay has activated
+	RelayActivations = promauto.NewCounter(prometheus.CounterOpts{
+		Name: "heater_element_activations_total",
+		Help: "The number of times the relay has been activated",
+	})
+
+	// CurrentTemperature is the current measured temperature in the boiler
+	CurrentTemperature = promauto.NewGauge(prometheus.GaugeOpts{
+		Name: "boiler_water_temperature_celsius",
+		Help: "The current temperature of the water in the boiler",
+	})
+
+	// SetpointTemperature is the desired temperature
+	SetpointTemperature = promauto.NewGauge(prometheus.GaugeOpts{
+		Name: "setpoint_temperature_celsius",
+		Help: "The current setpoint temperature",
+	})
+
+	// SensorFaultCount is the desired temperature
+	SensorFaultCount = promauto.NewCounter(prometheus.CounterOpts{
+		Name: "sensor_fault_total",
+		Help: "The number of times the sensor has read faulty data",
+	})
+)
+
+func exitProgram(w http.ResponseWriter, r *http.Request) {
+	os.Exit(0)
+}
+
+// PrometheusMetrics handles the prometheus scrape endpoint
+func PrometheusMetrics(prometheusPort int) {
+	http.Handle("/metrics", promhttp.Handler())
+	http.HandleFunc("/exit", exitProgram)
+	http.ListenAndServe(fmt.Sprintf(":%d", prometheusPort), nil)
+}