Commit 6a988a3b authored by Toasty's avatar Toasty

Working fallblattcontroller to say a message

parent f3de0161
Pipeline #991 failed with stage
in 6 minutes and 29 seconds
package fallblattadm
import (
"context"
"fmt"
"git.wegmueller.it/toasterson/fallblatt/controller"
"github.com/micro/go-micro"
"github.com/spf13/cobra"
)
var ctrlCmd = &cobra.Command{
Use: "controller-show-text",
Args: cobra.MinimumNArgs(2),
Use: "controller-say",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
natsURLFlag := cmd.Flag("msgbus")
client, err := controller.NewClient(natsURLFlag.Value.String(), args[0])
nameFlag := cmd.Flags().Lookup("name")
service := micro.NewService(
micro.Name("fallblatt"),
micro.Version(cmd.Version),
)
ctrl := controller.NewFallblattService(nameFlag.Value.String(), service.Client())
_, err := ctrl.Say(context.Background(), &controller.MessageRequest{
Message: args[0],
})
if err != nil {
return err
}
if err = client.SendCommand(0, controller.CommandShowText, map[string]string{"text": args[1]}); err != nil {
return err
}
fmt.Println("Sucess")
return nil
},
}
func init() {
f := ctrlCmd.Flags()
f.StringP("msgbus", "n", "nats://localhost:4222", "url of the nats server")
f.StringP("name", "n", "fallblatt", "name of the controller to contact")
mainCmd.AddCommand(ctrlCmd)
}
package fallblattcontroller
import (
"fmt"
"os"
"errors"
"git.wegmueller.it/toasterson/fallblatt/controller"
"github.com/spf13/cobra"
"github.com/micro/cli"
"github.com/micro/go-micro"
"github.com/micro/go-micro/config"
)
var ctrl *controller.Controller
var mainCmd = &cobra.Command{
Use: "fallblattcontroller",
RunE: runE,
}
func init() {
f := mainCmd.PersistentFlags()
f.StringP("msgbus", "n", "", "the nats server to listen on for requests")
f.StringP("config", "c", "", "the configuration file to read")
}
func Execute(version string) error {
mainCmd.Version = version
return mainCmd.Execute()
}
func runE(cmd *cobra.Command, _ []string) error {
natsFlag := cmd.Flag("msgbus")
if natsFlag.Value.String() == "" {
return fmt.Errorf("please provide %s flag", natsFlag.Name)
var configStruct controller.Config
var ctrl *controller.Controller
service := micro.NewService(
micro.Name("fallblatt"),
micro.Version(version),
micro.Flags(
cli.StringFlag{
Name: "config",
Usage: "The config file",
},
),
)
service.Init(
micro.Action(func(c *cli.Context) {
configFile := c.String("config")
if len(configFile) > 0 {
if err := config.LoadFile(configFile); err != nil {
panic(err)
}
if err := config.Scan(&configStruct); err != nil {
panic(err)
}
} else {
panic(errors.New("no config file set on commandline"))
}
}),
)
ctrl, err := controller.NewController(configStruct)
if err != nil {
return err
}
if f := cmd.Flag("config"); f != nil {
file, err := os.Open(f.Value.String())
if err != nil {
return fmt.Errorf("could not read configuration file %s: %s", f.Value.String(), err)
}
ctrl, err = controller.NewController(natsFlag.Value.String(), file)
_ = file.Close()
if err != nil {
return fmt.Errorf("could not create new Controller: %s", err)
}
} else {
var err error
ctrl, err = controller.NewController(natsFlag.Value.String(), nil)
if err != nil {
return fmt.Errorf("could not create controller: %s", err)
}
if err = ctrl.FetchConfigFromNats(); err != nil {
return fmt.Errorf("could not fetch config: %s", err)
}
if err := controller.RegisterFallblattHandler(
service.Server(),
ctrl,
); err != nil {
return err
}
defer ctrl.Close()
if err := ctrl.Listen(); err != nil {
return fmt.Errorf("error during runtime quitting: %s", err)
if err := service.Run(); err != nil {
return err
}
return nil
}
package controller
import "fmt"
type Request struct {
PanelId int
Command Command
Arguments map[string]string
}
type Command int
const (
CommandShowText Command = iota
CommandQuit
CommandReboot
)
type Response struct {
PanelId int
Code ResponseCode
Body string
}
func (r Response) Error() string {
return fmt.Sprintf("controller error %d: %s", r.Code, r.Body)
}
type ResponseCode int
const (
ResponseCodeSucess ResponseCode = iota
ResponseCodeFailure
ResponseCodeUnknownCommand
)
package controller
import (
"fmt"
"github.com/nats-io/nats.go"
)
type Client struct {
msgBus *nats.EncodedConn
sendChan chan Request
recvChan chan Response
recvSub *nats.Subscription
controllerName string
}
func NewClient(natsURL string, controllerName string) (*Client, error) {
nc, err := nats.Connect(natsURL)
if err != nil {
return nil, err
}
encConn, err := nats.NewEncodedConn(nc, nats.GOB_ENCODER)
c := &Client{msgBus: encConn}
if err := c.Open(controllerName); err != nil {
return nil, fmt.Errorf("could not create new client instance: %s", err)
}
return c, nil
}
func (c *Client) Open(controllerName string) error {
c.controllerName = controllerName
c.recvChan = make(chan Response)
c.sendChan = make(chan Request)
var err error
c.recvSub, err = c.msgBus.BindRecvChan(fmt.Sprintf(ResponseChannelPattern, controllerName), c.recvChan)
if err != nil {
return err
}
err = c.msgBus.BindSendChan(fmt.Sprintf(RequestChannelPattern, controllerName), c.sendChan)
if err != nil {
return err
}
return nil
}
func (c *Client) SendCommand(panelId int, command Command, args map[string]string) error {
c.sendChan <- Request{
PanelId: panelId,
Command: command,
Arguments: args,
}
resp, ok := <-c.recvChan
if !ok {
return fmt.Errorf("channel to controller %s already closed", c.controllerName)
}
if resp.Code == ResponseCodeSucess {
return nil
}
return resp
}
package controller
import (
"encoding/json"
"fmt"
"io"
"git.wegmueller.it/toasterson/fallblatt/panel"
)
type Config struct {
Name string `json:"name"`
Panels []panel.Config `json:"panels"`
Name string `json:"name"`
Panel panel.Config `json:"panel"`
}
func (c *Controller) parseConfig(cfgR io.Reader) error {
var cfg Config
if err := json.NewDecoder(cfgR).Decode(&cfg); err != nil {
return fmt.Errorf("cannot decode controller configuration: %s", err)
}
func (c *Controller) parseConfig(cfg Config) error {
c.name = cfg.Name
for _, p := range cfg.Panels {
pan, err := panel.NewPanelFromConfig(p)
if err != nil {
return err
}
c.panels = append(c.panels, pan)
pan, err := panel.NewPanelFromConfig(cfg.Panel)
if err != nil {
return err
}
c.panel = pan
return nil
}
This diff is collapsed.
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: fallblattcontroller.proto
package controller
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
import (
context "context"
client "github.com/micro/go-micro/client"
server "github.com/micro/go-micro/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ client.Option
var _ server.Option
// Client API for Fallblatt service
type FallblattService interface {
Say(ctx context.Context, in *MessageRequest, opts ...client.CallOption) (*APIResponse, error)
SetId(ctx context.Context, in *SetIdRequest, opts ...client.CallOption) (*APIResponse, error)
SetPosition(ctx context.Context, in *SetPosRequest, opts ...client.CallOption) (*APIResponse, error)
Calibrate(ctx context.Context, in *CalibrateRequest, opts ...client.CallOption) (*APIResponse, error)
PanelStatus(ctx context.Context, in *PanelStatusRequest, opts ...client.CallOption) (*PanelStatusResponse, error)
Clear(ctx context.Context, in *ClearRequest, opts ...client.CallOption) (*APIResponse, error)
}
type fallblattService struct {
c client.Client
name string
}
func NewFallblattService(name string, c client.Client) FallblattService {
if c == nil {
c = client.NewClient()
}
if len(name) == 0 {
name = "fallblatt"
}
return &fallblattService{
c: c,
name: name,
}
}
func (c *fallblattService) Say(ctx context.Context, in *MessageRequest, opts ...client.CallOption) (*APIResponse, error) {
req := c.c.NewRequest(c.name, "Fallblatt.Say", in)
out := new(APIResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *fallblattService) SetId(ctx context.Context, in *SetIdRequest, opts ...client.CallOption) (*APIResponse, error) {
req := c.c.NewRequest(c.name, "Fallblatt.SetId", in)
out := new(APIResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *fallblattService) SetPosition(ctx context.Context, in *SetPosRequest, opts ...client.CallOption) (*APIResponse, error) {
req := c.c.NewRequest(c.name, "Fallblatt.SetPosition", in)
out := new(APIResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *fallblattService) Calibrate(ctx context.Context, in *CalibrateRequest, opts ...client.CallOption) (*APIResponse, error) {
req := c.c.NewRequest(c.name, "Fallblatt.Calibrate", in)
out := new(APIResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *fallblattService) PanelStatus(ctx context.Context, in *PanelStatusRequest, opts ...client.CallOption) (*PanelStatusResponse, error) {
req := c.c.NewRequest(c.name, "Fallblatt.PanelStatus", in)
out := new(PanelStatusResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *fallblattService) Clear(ctx context.Context, in *ClearRequest, opts ...client.CallOption) (*APIResponse, error) {
req := c.c.NewRequest(c.name, "Fallblatt.Clear", in)
out := new(APIResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Fallblatt service
type FallblattHandler interface {
Say(context.Context, *MessageRequest, *APIResponse) error
SetId(context.Context, *SetIdRequest, *APIResponse) error
SetPosition(context.Context, *SetPosRequest, *APIResponse) error
Calibrate(context.Context, *CalibrateRequest, *APIResponse) error
PanelStatus(context.Context, *PanelStatusRequest, *PanelStatusResponse) error
Clear(context.Context, *ClearRequest, *APIResponse) error
}
func RegisterFallblattHandler(s server.Server, hdlr FallblattHandler, opts ...server.HandlerOption) error {
type fallblatt interface {
Say(ctx context.Context, in *MessageRequest, out *APIResponse) error
SetId(ctx context.Context, in *SetIdRequest, out *APIResponse) error
SetPosition(ctx context.Context, in *SetPosRequest, out *APIResponse) error
Calibrate(ctx context.Context, in *CalibrateRequest, out *APIResponse) error
PanelStatus(ctx context.Context, in *PanelStatusRequest, out *PanelStatusResponse) error
Clear(ctx context.Context, in *ClearRequest, out *APIResponse) error
}
type Fallblatt struct {
fallblatt
}
h := &fallblattHandler{hdlr}
return s.Handle(s.NewHandler(&Fallblatt{h}, opts...))
}
type fallblattHandler struct {
FallblattHandler
}
func (h *fallblattHandler) Say(ctx context.Context, in *MessageRequest, out *APIResponse) error {
return h.FallblattHandler.Say(ctx, in, out)
}
func (h *fallblattHandler) SetId(ctx context.Context, in *SetIdRequest, out *APIResponse) error {
return h.FallblattHandler.SetId(ctx, in, out)
}
func (h *fallblattHandler) SetPosition(ctx context.Context, in *SetPosRequest, out *APIResponse) error {
return h.FallblattHandler.SetPosition(ctx, in, out)
}
func (h *fallblattHandler) Calibrate(ctx context.Context, in *CalibrateRequest, out *APIResponse) error {
return h.FallblattHandler.Calibrate(ctx, in, out)
}
func (h *fallblattHandler) PanelStatus(ctx context.Context, in *PanelStatusRequest, out *PanelStatusResponse) error {
return h.FallblattHandler.PanelStatus(ctx, in, out)
}
func (h *fallblattHandler) Clear(ctx context.Context, in *ClearRequest, out *APIResponse) error {
return h.FallblattHandler.Clear(ctx, in, out)
}
syntax = "proto3";
option go_package = "controller";
service Fallblatt {
rpc Say(MessageRequest) returns (APIResponse) {}
rpc SetId(SetIdRequest) returns (APIResponse) {}
rpc SetPosition(SetPosRequest) returns (APIResponse) {}
rpc Calibrate(CalibrateRequest) returns (APIResponse) {}
rpc PanelStatus(PanelStatusRequest) returns (PanelStatusResponse) {}
rpc Clear(ClearRequest) returns (APIResponse) {}
}
message APIResponse {
enum StatusCode {
SUCCESS = 0;
FAILURE = 1;
}
StatusCode code = 1;
string error_message = 2;
}
message MessageRequest {
string message = 1;
}
message SetIdRequest {
int32 addr = 1;
int32 new_id = 2;
}
message SetPosRequest {
int32 addr = 1;
int32 pos = 2;
string string_pos = 3;
}
message CalibrateRequest {
int32 addr = 1;
}
message PanelStatusRequest {
int32 addr = 1;
}
message PanelStatusResponse {
string serial_number = 1;
int32 addr = 2;
int32 pos = 3;
string type = 4;
}
message ClearRequest {
int32 addr = 1;
}
\ No newline at end of file
//go:generate protoc --proto_path=$GOPATH/src:. --micro_out=. --go_out=. fallblattcontroller.proto
package controller
package controller
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"time"
"context"
"git.wegmueller.it/toasterson/fallblatt/panel"
"github.com/nats-io/nats.go"
)
const (
RequestChannelPattern = "fallblatt.controller.%s.request"
ResponseChannelPattern = "fallblatt.controller.%s.response"
)
type Controller struct {
name string
msgBus *nats.EncodedConn
panels []*panel.Panel
recvChan chan Request
sendChan chan Response
name string
panel *panel.Panel
}
func NewController(natsUrl string, panelsConfig io.Reader) (*Controller, error) {
nc, err := nats.Connect(natsUrl)
if err != nil {
return nil, err
}
encConn, err := nats.NewEncodedConn(nc, nats.GOB_ENCODER)
if err != nil {
return nil, fmt.Errorf("could not create new Controller instance: %s", err)
}
ctrl := &Controller{msgBus: encConn}
if panelsConfig != nil {
err = ctrl.parseConfig(panelsConfig)
if err != nil {
return nil, err
}
func (c *Controller) Say(ctx context.Context, in *MessageRequest, response *APIResponse) error {
if err := c.panel.SetText(in.Message); err != nil {
*response = APIResponse{Code: APIResponse_FAILURE, ErrorMessage: err.Error()}
return err
}
return ctrl, nil
return nil
}
func (c *Controller) FetchConfigFromNats() error {
hName, err := os.Hostname()
if err != nil {
return fmt.Errorf("could not fetch hostname to get config: %s", err)
}
var rVal json.RawMessage
if err = c.msgBus.Request("fallblattcontroller.register", hName, &rVal, 5*time.Second); err != nil {
return fmt.Errorf("error while getting config via NATS: %s", err)
}
if strings.HasPrefix(string(rVal), "failed to register:") {
return fmt.Errorf("could not register controller %s: %s", c.name, string(rVal))
}
return c.parseConfig(bytes.NewReader(rVal))
func (c *Controller) SetId(ctx context.Context, in *SetIdRequest, response *APIResponse) error {
panic("implement me")
}
func (c *Controller) Close() {
_ = c.msgBus.Publish("fallblattcontroller.unregister", c.name)
c.msgBus.Close()
func (c *Controller) SetPosition(ctx context.Context, in *SetPosRequest, response *APIResponse) error {
panic("implement me")
}
func (c *Controller) Listen() (err error) {
c.recvChan = make(chan Request)
c.sendChan = make(chan Response)
recvSub, err := c.msgBus.BindRecvQueueChan(fmt.Sprintf(RequestChannelPattern, c.name), c.name, c.recvChan)
if err != nil {
return err
}
err = c.msgBus.BindSendChan(fmt.Sprintf(ResponseChannelPattern, c.name), c.sendChan)
if err != nil {
return err
}
defer func() {
close(c.sendChan)
uerr := recvSub.Unsubscribe()
if uerr != nil {
err = uerr
}
}()
for req := range c.recvChan {
switch req.Command {
case CommandShowText:
c.setText(req)
case CommandQuit:
close(c.recvChan)
case CommandReboot:
time.Sleep(5 * time.Second)
if err := c.FetchConfigFromNats(); err != nil {
return fmt.Errorf("failed to get config after reboot issued: %s", err)
}
default:
c.unknownCommand(req)
}
}
return nil
func (c *Controller) Calibrate(ctx context.Context, in *CalibrateRequest, response *APIResponse) error {
panic("implement me")
}
func (c *Controller) unknownCommand(req Request) {
var retVal string
if humanCommand, ok := req.Arguments["human-command"]; ok {
retVal = fmt.Sprintf("command %s not known on the server", humanCommand)
} else {
retVal = fmt.Sprintf("command %d not known on the server", req.Command)
}
c.sendChan <- Response{
Code: ResponseCodeUnknownCommand,
Body: retVal,
func (c *Controller) PanelStatus(ctx context.Context, in *PanelStatusRequest, response *PanelStatusResponse) error {
panic("implement me")
}
func (c *Controller) Clear(ctx context.Context, in *ClearRequest, response *APIResponse) error {
panic("implement me")