Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf903c1d71 | ||
|
|
19eb205ee3 | ||
|
|
edb45c74b6 | ||
|
|
c4b391db66 | ||
|
|
3bf8cbef2f | ||
|
|
5db753e401 | ||
|
|
2a750ae1f7 |
+1
-1
@@ -11,7 +11,7 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
db := config.DBX().DB
|
db := config.DBX().DB
|
||||||
|
|
||||||
log.Printf("Target version: %s",time.Now().Format("20060102150405"))
|
log.Printf("Target version: %s", time.Now().Format("20060102150405"))
|
||||||
log.Printf("Database: %s:%d/%s", config.MySqlHost(), config.MySqlPort(), config.MySqlSchema())
|
log.Printf("Database: %s:%d/%s", config.MySqlHost(), config.MySqlPort(), config.MySqlSchema())
|
||||||
log.Printf("Authenticating as: %s", config.MySqlUsername())
|
log.Printf("Authenticating as: %s", config.MySqlUsername())
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ EVENT HANDLER FACTORY COMMANDS
|
|||||||
|
|
||||||
factory run <file.yaml>
|
factory run <file.yaml>
|
||||||
|
|
||||||
|
MISC COMMANDS
|
||||||
|
|
||||||
|
color <color>
|
||||||
|
|
||||||
File example (use '---' to delimit multiple configs in one file):
|
File example (use '---' to delimit multiple configs in one file):
|
||||||
mode: SceneDimmer
|
mode: SceneDimmer
|
||||||
target: tag:Soverom
|
target: tag:Soverom
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.aiterp.net/lucifer/new-server/app/client"
|
"git.aiterp.net/lucifer/new-server/app/client"
|
||||||
|
"git.aiterp.net/lucifer/new-server/internal/color"
|
||||||
"git.aiterp.net/lucifer/new-server/models"
|
"git.aiterp.net/lucifer/new-server/models"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@@ -92,6 +93,29 @@ func main() {
|
|||||||
|
|
||||||
WriteDeviceInfoTable(os.Stdout, devices)
|
WriteDeviceInfoTable(os.Stdout, devices)
|
||||||
}
|
}
|
||||||
|
case "color":
|
||||||
|
{
|
||||||
|
col, err := color.Parse(cmd.Params.Get(0).StringOr("hs:0,0"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if col.IsKelvin() {
|
||||||
|
fmt.Println(col.String())
|
||||||
|
}
|
||||||
|
if hs, ok := col.ToHS(); ok {
|
||||||
|
fmt.Println(hs.String())
|
||||||
|
}
|
||||||
|
if hsk, ok := col.ToHSK(); ok {
|
||||||
|
fmt.Println(hsk.String())
|
||||||
|
}
|
||||||
|
if xy, ok := col.ToXY(); ok {
|
||||||
|
fmt.Println(xy.String())
|
||||||
|
}
|
||||||
|
if rgb, ok := col.ToRGB(); ok {
|
||||||
|
fmt.Println(rgb.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case "factory":
|
case "factory":
|
||||||
factoryCmd(ctx, c)
|
factoryCmd(ctx, c)
|
||||||
|
|||||||
@@ -252,6 +252,9 @@ func (b *Bridge) MakeCongruent(ctx context.Context) (int, error) {
|
|||||||
updates := make(map[string]ResourceUpdate)
|
updates := make(map[string]ResourceUpdate)
|
||||||
for _, device := range b.devices {
|
for _, device := range b.devices {
|
||||||
resource := b.resources[device.InternalID]
|
resource := b.resources[device.InternalID]
|
||||||
|
if resource == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Update device
|
// Update device
|
||||||
if resource.Metadata.Name != device.Name {
|
if resource.Metadata.Name != device.Name {
|
||||||
@@ -619,3 +622,18 @@ func (b *Bridge) SearchDevices(ctx context.Context, timeout time.Duration) ([]mo
|
|||||||
// Return said devices.
|
// Return said devices.
|
||||||
return newDevices, nil
|
return newDevices, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Bridge) Forget(ctx context.Context, device models.Device) error {
|
||||||
|
b.mu.Lock()
|
||||||
|
resource := b.resources[device.InternalID]
|
||||||
|
b.mu.Unlock()
|
||||||
|
|
||||||
|
if resource == nil {
|
||||||
|
return errors.New("resource not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.client.DeleteResource(ctx, ResourceLink{
|
||||||
|
ID: device.InternalID,
|
||||||
|
Kind: "device",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ func (c *Client) UpdateResource(ctx context.Context, link ResourceLink, update R
|
|||||||
return c.put(ctx, link.Path(), update, nil)
|
return c.put(ctx, link.Path(), update, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteResource(ctx context.Context, link ResourceLink) error {
|
||||||
|
return c.delete(ctx, link.Path(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) LegacyDiscover(ctx context.Context, kind string) error {
|
func (c *Client) LegacyDiscover(ctx context.Context, kind string) error {
|
||||||
return c.legacyPost(ctx, kind, nil, nil)
|
return c.legacyPost(ctx, kind, nil, nil)
|
||||||
}
|
}
|
||||||
@@ -268,6 +272,36 @@ func (c *Client) post(ctx context.Context, path string, body interface{}, target
|
|||||||
return json.NewDecoder(res.Body).Decode(&target)
|
return json.NewDecoder(res.Body).Decode(&target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) delete(ctx context.Context, path string, target interface{}) error {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-c.ch:
|
||||||
|
defer func() {
|
||||||
|
c.ch <- struct{}{}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", fmt.Sprintf("https://%s/%s", c.host, path), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("hue-application-key", c.token)
|
||||||
|
|
||||||
|
res, err := httpClient.Do(req.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if target == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.NewDecoder(res.Body).Decode(&target)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) legacyPost(ctx context.Context, resource string, body interface{}, target interface{}) error {
|
func (c *Client) legacyPost(ctx context.Context, resource string, body interface{}, target interface{}) error {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -305,6 +339,38 @@ func (c *Client) legacyPost(ctx context.Context, resource string, body interface
|
|||||||
return json.NewDecoder(res.Body).Decode(target)
|
return json.NewDecoder(res.Body).Decode(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) legacyDelete(ctx context.Context, resource string, target interface{}) error {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-c.ch:
|
||||||
|
defer func() {
|
||||||
|
c.ch <- struct{}{}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.token != "" {
|
||||||
|
resource = c.token + "/" + resource
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("DELETE", fmt.Sprintf("http://%s/api/%s", c.host, resource), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := httpClient.Do(req.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if target == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.NewDecoder(res.Body).Decode(target)
|
||||||
|
}
|
||||||
|
|
||||||
func reqBody(body interface{}) (io.Reader, error) {
|
func reqBody(body interface{}) (io.Reader, error) {
|
||||||
if body == nil {
|
if body == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@@ -100,6 +100,10 @@ func (d *Driver) Run(ctx context.Context, bridge models.Bridge, ch chan<- models
|
|||||||
return d.ensureBridge(bridge).Run(ctx, ch)
|
return d.ensureBridge(bridge).Run(ctx, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Driver) ForgetDevice(ctx context.Context, bridge models.Bridge, device models.Device) error {
|
||||||
|
return d.ensureBridge(bridge).Forget(ctx, device)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Driver) ensureBridge(info models.Bridge) *Bridge {
|
func (d *Driver) ensureBridge(info models.Bridge) *Bridge {
|
||||||
d.mu.Lock()
|
d.mu.Lock()
|
||||||
for _, bridge := range d.bridges {
|
for _, bridge := range d.bridges {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.aiterp.net/lucifer/new-server/internal/lerrors"
|
"git.aiterp.net/lucifer/new-server/internal/lerrors"
|
||||||
"git.aiterp.net/lucifer/new-server/models"
|
"git.aiterp.net/lucifer/new-server/models"
|
||||||
@@ -83,11 +84,16 @@ func (b *bridge) listDevices(ctx context.Context) ([]models.Device, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *bridge) pushStateChange(ctx context.Context, deviceModel models.Device) error {
|
func (b *bridge) pushStateChange(ctx context.Context, deviceModel models.Device) error {
|
||||||
|
if ip, ok := deviceModel.DriverProperties["ip"].(string); ok {
|
||||||
|
return b.pushWifiStateChange(ctx, ip, deviceModel.State.Temperature)
|
||||||
|
}
|
||||||
|
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
if b.luciferMillIDMap == nil {
|
if b.luciferMillIDMap == nil {
|
||||||
b.luciferMillIDMap = make(map[int]int, 4)
|
b.luciferMillIDMap = make(map[int]int, 4)
|
||||||
b.millLuciferIDMap = make(map[int]int, 4)
|
b.millLuciferIDMap = make(map[int]int, 4)
|
||||||
}
|
}
|
||||||
|
subDomain := deviceModel.DriverProperties["subDomain"].(string)
|
||||||
|
|
||||||
if b.luciferMillIDMap[deviceModel.ID] == 0 {
|
if b.luciferMillIDMap[deviceModel.ID] == 0 {
|
||||||
millID, _ := strconv.Atoi(deviceModel.InternalID)
|
millID, _ := strconv.Atoi(deviceModel.InternalID)
|
||||||
@@ -100,28 +106,90 @@ func (b *bridge) pushStateChange(ctx context.Context, deviceModel models.Device)
|
|||||||
if deviceModel.State.Power {
|
if deviceModel.State.Power {
|
||||||
status = 1
|
status = 1
|
||||||
}
|
}
|
||||||
powerReq := deviceControlReqBody{
|
|
||||||
SubDomain: deviceModel.DriverProperties["subDomain"].(string),
|
if subDomainIsGen2(subDomain) {
|
||||||
DeviceID: b.luciferMillIDMap[deviceModel.ID],
|
powerReq := deviceControlReqBody{
|
||||||
TestStatus: 1,
|
SubDomain: subDomain,
|
||||||
Status: status,
|
DeviceID: b.luciferMillIDMap[deviceModel.ID],
|
||||||
|
TestStatus: 1,
|
||||||
|
Status: status,
|
||||||
|
}
|
||||||
|
err := b.command(ctx, "deviceControl", powerReq, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tempReq := changeInfoReqBody{
|
||||||
|
DeviceID: b.luciferMillIDMap[deviceModel.ID],
|
||||||
|
Value: deviceModel.State.Temperature,
|
||||||
|
TimeZoneNum: "+02:00",
|
||||||
|
Key: "holidayTemp",
|
||||||
|
}
|
||||||
|
err = b.command(ctx, "changeDeviceInfo", tempReq, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sd, _ := strconv.Atoi(subDomain)
|
||||||
|
|
||||||
|
tempReq := deviceControlGen3Body{
|
||||||
|
Operation: "SINGLE_CONTROL",
|
||||||
|
Status: status,
|
||||||
|
SubDomain: sd,
|
||||||
|
DeviceId: b.luciferMillIDMap[deviceModel.ID],
|
||||||
|
HoldTemp: deviceModel.State.Temperature,
|
||||||
|
}
|
||||||
|
err := b.command(ctx, "deviceControlGen3ForApp", tempReq, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
powerReq := deviceControlGen3Body{
|
||||||
|
Operation: "SWITCH",
|
||||||
|
Status: status,
|
||||||
|
SubDomain: sd,
|
||||||
|
DeviceId: b.luciferMillIDMap[deviceModel.ID],
|
||||||
|
}
|
||||||
|
err = b.command(ctx, "deviceControlGen3ForApp", powerReq, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
err := b.command(ctx, "deviceControl", powerReq, nil)
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridge) pushWifiStateChange(ctx context.Context, ip string, temperature int) error {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
data, err := json.Marshal(wifiSetTemperatureBody{
|
||||||
|
Type: "Normal",
|
||||||
|
Value: temperature,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tempReq := changeInfoReqBody{
|
req, err := http.NewRequestWithContext(
|
||||||
DeviceID: b.luciferMillIDMap[deviceModel.ID],
|
ctx,
|
||||||
Value: deviceModel.State.Temperature,
|
"POST",
|
||||||
TimeZoneNum: "+02:00",
|
fmt.Sprintf("http://%s/set-temperature", ip),
|
||||||
Key: "holidayTemp",
|
bytes.NewReader(data),
|
||||||
}
|
)
|
||||||
err = b.command(ctx, "changeDeviceInfo", tempReq, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode > 299 {
|
||||||
|
return errors.New("mill: negative response from " + ip)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,3 +307,15 @@ func addDefaultHeaders(req *http.Request) {
|
|||||||
req.Header.Add("X-Zc-Seq-Id", "1")
|
req.Header.Add("X-Zc-Seq-Id", "1")
|
||||||
req.Header.Add("X-Zc-Version", "1")
|
req.Header.Add("X-Zc-Version", "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gen2subDomains = []string{"863", "5316", "5317", "5332", "5333", "6933"}
|
||||||
|
|
||||||
|
func subDomainIsGen2(subDomain string) bool {
|
||||||
|
for _, gen2sd := range gen2subDomains {
|
||||||
|
if subDomain == gen2sd {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,6 +65,19 @@ type deviceControlReqBody struct {
|
|||||||
PowerLevel int `json:"powerLevel"`
|
PowerLevel int `json:"powerLevel"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type deviceControlGen3Body struct {
|
||||||
|
Operation string `json:"operation"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
SubDomain int `json:"subDomain"`
|
||||||
|
DeviceId int `json:"deviceId"`
|
||||||
|
HoldTemp int `json:"holdTemp,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wifiSetTemperatureBody struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value int `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
var location *time.Location
|
var location *time.Location
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
Reference in New Issue
Block a user