366 lines
9.6 KiB
Go
366 lines
9.6 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"time"
|
|
|
|
"forge.cadoles.com/Pyxis/orion/emlid"
|
|
"forge.cadoles.com/Pyxis/orion/emlid/updater"
|
|
"forge.cadoles.com/Pyxis/orion/openwrt"
|
|
"github.com/gorilla/rpc"
|
|
"github.com/gorilla/rpc/json"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// OrionService is the JSON-RPC API
|
|
type OrionService struct {
|
|
UCI *openwrt.UCI
|
|
}
|
|
|
|
// NewOrionService create a new OrionService !
|
|
func NewOrionService() *OrionService {
|
|
uci := openwrt.NewUCI()
|
|
return &OrionService{
|
|
UCI: uci,
|
|
}
|
|
}
|
|
|
|
// UCIArgs argument structure for exported method OwrtListWifiDevices
|
|
type UCIArgs struct{}
|
|
|
|
// UCIResponse is the response structure for exposed method OwrtListWifiDevices
|
|
type UCIResponse struct {
|
|
Devices []map[string]string
|
|
}
|
|
|
|
// OwrtListWifiDevices offers an RPC Method to list Wifi nics in a OpenWRT device.
|
|
func (o *OrionService) OwrtListWifiDevices(r *http.Request, args *UCIArgs, reply *UCIResponse) error {
|
|
o.UCI.LoadWirelessConf()
|
|
devs := o.UCI.GetWifiDevices()
|
|
reply.Devices = devs
|
|
return nil
|
|
}
|
|
|
|
// ListIfaceArgs argument structure for exported method OwrtListWifiDevices
|
|
type ListIfaceArgs struct{}
|
|
|
|
// ListIfaceResponse is the response structure for exposed method OwrtListWifiDevices
|
|
type ListIfaceResponse struct {
|
|
Interfaces []*openwrt.UCIWirelessInterface
|
|
}
|
|
|
|
// OwrtListWifiInterfaces offers an RPC Method to list wifi interfaces in a OpenWRT device.
|
|
func (o *OrionService) OwrtListWifiInterfaces(r *http.Request, args *ListIfaceArgs, reply *ListIfaceResponse) error {
|
|
o.UCI.LoadWirelessConf()
|
|
reply.Interfaces = o.UCI.GetWifiIfaces()
|
|
return nil
|
|
}
|
|
|
|
// CreateIfaceArgs argument structure for exported method OwrtCreateWifiInterface
|
|
type CreateIfaceArgs struct {
|
|
Iface *openwrt.UCIWirelessInterface
|
|
Cleanup bool
|
|
}
|
|
|
|
// CreateIfaceResponse argument structure for exported method OwrtCreateWifiInterface
|
|
type CreateIfaceResponse struct {
|
|
Iface *openwrt.UCIWirelessInterface
|
|
Errors []*openwrt.Action
|
|
}
|
|
|
|
// OwrtCreateWifiInterface Create a WifiInterface in openwrt
|
|
func (o *OrionService) OwrtCreateWifiInterface(r *http.Request,
|
|
args *CreateIfaceArgs,
|
|
reply *CreateIfaceResponse) error {
|
|
reply.Iface = nil
|
|
|
|
if args.Cleanup {
|
|
o.UCI.LoadWirelessConf()
|
|
w := o.UCI.GetWifiIfaces()
|
|
for _, iface := range w {
|
|
iface.Delete(o.UCI)
|
|
}
|
|
o.UCI.Commit()
|
|
o.UCI.Reload()
|
|
time.Sleep(3 * time.Second)
|
|
}
|
|
|
|
o.UCI.LoadWirelessConf()
|
|
create := args.Iface.Create(o.UCI)
|
|
if create.ReturnCode != 0 {
|
|
reply.Errors = append(reply.Errors, create)
|
|
return nil
|
|
}
|
|
create = args.Iface.SysAdd(o.UCI)
|
|
if create.ReturnCode == 233 {
|
|
delete := args.Iface.SysDel(o.UCI)
|
|
if delete.ReturnCode != 0 {
|
|
reply.Errors = append(reply.Errors, delete)
|
|
return nil
|
|
}
|
|
create = args.Iface.SysAdd(o.UCI)
|
|
if create.ReturnCode != 0 {
|
|
reply.Errors = append(reply.Errors, create)
|
|
return nil
|
|
}
|
|
}
|
|
create = args.Iface.Up(o.UCI)
|
|
if create.ReturnCode != 0 {
|
|
reply.Errors = append(reply.Errors, create)
|
|
return nil
|
|
}
|
|
create = args.Iface.Save(o.UCI)
|
|
if create.ReturnCode != 0 {
|
|
reply.Errors = append(reply.Errors, create)
|
|
return nil
|
|
}
|
|
reply.Iface = args.Iface
|
|
return nil
|
|
}
|
|
|
|
// ConnectIfaceArgs argument structure for exported method OwrtCreateWifiInterface
|
|
type ConnectIfaceArgs struct {
|
|
Iface *openwrt.UCIWirelessInterface
|
|
SSID string
|
|
Key string
|
|
}
|
|
|
|
// ConnectIfaceResponse argument structure for exported method OwrtCreateWifiInterface
|
|
type ConnectIfaceResponse struct {
|
|
status int
|
|
Errors []string
|
|
}
|
|
|
|
// OwrtConnectWifiInterface connects a given Wifi Interface to a given SSID
|
|
func (o *OrionService) OwrtConnectWifiInterface(r *http.Request,
|
|
args *ConnectIfaceArgs,
|
|
reply *ConnectIfaceResponse) error {
|
|
o.UCI.LoadWirelessConf()
|
|
iface := o.UCI.GetWifiIface(args.Iface.Index)
|
|
cells := iface.Scan()
|
|
|
|
for _, cell := range cells {
|
|
if cell.Ssid == args.SSID {
|
|
cn := iface.Connect(o.UCI, cell, args.Key)
|
|
if cn.ReturnCode != 0 {
|
|
reply.status = cn.ReturnCode
|
|
reply.Errors = append(reply.Errors, cn.Stdout, cn.Stderr, cn.Command)
|
|
}
|
|
reply.status = 0
|
|
return nil
|
|
}
|
|
}
|
|
reply.status = 1
|
|
msg := fmt.Sprintf("Wifi Cell with SSID %s is not available !", args.SSID)
|
|
reply.Errors = append(reply.Errors, msg)
|
|
return nil
|
|
}
|
|
|
|
// OrionBox describe an fresh Orion box (base or rover)
|
|
type OrionBox struct {
|
|
Address string
|
|
NewAddress string
|
|
SSID string
|
|
Security string
|
|
WifiKey string
|
|
}
|
|
|
|
// OrionServer describe the Orion master server
|
|
type OrionServer struct {
|
|
Address string
|
|
SSID string
|
|
Security string
|
|
WifiKey string
|
|
ClientIface *openwrt.UCIWirelessInterface
|
|
}
|
|
|
|
// UpdateOrionBoxArgs argument structure for exported method OwrtCreateWifiInterface
|
|
type UpdateOrionBoxArgs struct {
|
|
Box *OrionBox
|
|
Server *OrionServer
|
|
}
|
|
|
|
// UpdateOrionBoxResponse argument structure for exported method OwrtCreateWifiInterface
|
|
type UpdateOrionBoxResponse struct {
|
|
IP string
|
|
Netmask string
|
|
Synced bool
|
|
Version string
|
|
Status *updater.UpdateStatus
|
|
Results *updater.TestResults
|
|
}
|
|
|
|
// Connect wifi interface to a Orion box wifi hotspot!
|
|
func (o *OrionService) connectBox(box *OrionBox, server *OrionServer) error {
|
|
o.UCI.LoadWirelessConf()
|
|
if server == nil {
|
|
return fmt.Errorf("Server definition is empty")
|
|
}
|
|
|
|
if box == nil {
|
|
return fmt.Errorf("Box definitioni is emtpy")
|
|
}
|
|
iface := server.ClientIface
|
|
cells := iface.Scan()
|
|
for _, cell := range cells {
|
|
if cell.Ssid == box.SSID {
|
|
cn := iface.Connect(o.UCI, cell, box.WifiKey)
|
|
if cn.ReturnCode != 0 {
|
|
return fmt.Errorf("%s\n%s", cn.Stdout, cn.Stderr)
|
|
}
|
|
dhcli := openwrt.NewDhcpClient(iface.SysDevName)
|
|
dhres := dhcli.AskForIP()
|
|
if dhres.CmdRes.ReturnCode != 0 {
|
|
return fmt.Errorf("%s\n%s", cn.Stdout, cn.Stderr)
|
|
}
|
|
re := o.UCI.Reload()
|
|
if re.ReturnCode != 0 {
|
|
return fmt.Errorf("%s\n%s", re.Stdout, re.Stderr)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("Wifi cell with SSID %s is not available", box.SSID)
|
|
}
|
|
|
|
func (o *OrionService) discoverService() ([]emlid.Service, error) {
|
|
return emlid.Discover(20 * time.Second)
|
|
}
|
|
|
|
// setupMasterWifi connects to the box, add wifi network and join it !
|
|
func (o *OrionService) setupMasterWifi(rqContext context.Context, box *OrionBox, server *OrionServer) error {
|
|
if err := o.connectBox(box, server); err != nil {
|
|
return errors.Wrap(err, "Connect to box failed")
|
|
}
|
|
|
|
if server.Security == "" {
|
|
server.Security = string(updater.SecurityWPAPSK)
|
|
}
|
|
|
|
var boxCli *updater.Client
|
|
service, err := o.discoverService()
|
|
if len(service) == 0 {
|
|
boxCli = updater.NewClient(
|
|
emlid.WithEndpoint(box.Address, 80),
|
|
)
|
|
} else if err != nil {
|
|
boxCli = updater.NewClient(
|
|
emlid.WithService(service[0]),
|
|
)
|
|
} else {
|
|
return err
|
|
}
|
|
|
|
if err := boxCli.Connect(); err != nil {
|
|
return errors.Wrap(err, "Connecting to Box failed")
|
|
}
|
|
defer boxCli.Close()
|
|
|
|
ctx, testResultsCancel := context.WithTimeout(rqContext, 55*time.Second)
|
|
defer testResultsCancel()
|
|
_, err = boxCli.TestResults(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Minimal test failed")
|
|
}
|
|
|
|
ctx, addWifiCancel := context.WithTimeout(rqContext, 55*time.Second)
|
|
defer addWifiCancel()
|
|
|
|
done, err := boxCli.AddWifiNetwork(ctx, server.SSID, updater.WifiSecurity(server.Security), server.WifiKey)
|
|
if err != nil {
|
|
return errors.Wrap(err, "AddWifiNetworkFailed")
|
|
}
|
|
|
|
if !done {
|
|
return fmt.Errorf("Impossible to add wifi network")
|
|
}
|
|
|
|
ctx, joinWifiCancel := context.WithTimeout(ctx, 55*time.Second)
|
|
defer joinWifiCancel()
|
|
|
|
if err := boxCli.JoinWifiNetwork(ctx, server.SSID, true); err != nil {
|
|
return errors.Wrap(err, "Time sync failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// updateAndReboot connects to the box with the New adress and run basic tests on version dans updates
|
|
func (o *OrionService) updateAndReboot(rqContext context.Context, box *OrionBox, server *OrionServer, reply *UpdateOrionBoxResponse) error {
|
|
var boxCli *updater.Client
|
|
service, err := o.discoverService()
|
|
if err != nil {
|
|
boxCli = updater.NewClient(
|
|
emlid.WithService(service[0]),
|
|
)
|
|
} else {
|
|
boxCli = updater.NewClient(
|
|
emlid.WithEndpoint(box.NewAddress, 80),
|
|
)
|
|
}
|
|
|
|
if err := boxCli.Connect(); err != nil {
|
|
if err := boxCli.Connect(); err != nil {
|
|
return errors.Wrap(err, "Connecting to Box on master wifi network failed")
|
|
}
|
|
}
|
|
defer boxCli.Close()
|
|
|
|
ctx, timeSyncedCancel := context.WithTimeout(rqContext, 55*time.Second)
|
|
defer timeSyncedCancel()
|
|
synced, err := boxCli.TimeSynced(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Time sync failed")
|
|
}
|
|
reply.Synced = synced
|
|
|
|
ctx, reachviewVersionCancel := context.WithTimeout(rqContext, 55*time.Second)
|
|
defer reachviewVersionCancel()
|
|
version, err := boxCli.ReachViewVersion(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
reply.Version = version
|
|
|
|
ctx, updateCancel := context.WithTimeout(rqContext, 55*time.Second)
|
|
defer updateCancel()
|
|
status, err := boxCli.Update(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "System update failed")
|
|
}
|
|
reply.Status = status
|
|
|
|
if err := boxCli.SkipUpdate(); err != nil {
|
|
log.Fatalf("error while skipping update: %s", err)
|
|
}
|
|
|
|
ctx, rebootCancel := context.WithTimeout(rqContext, 55*time.Second)
|
|
defer rebootCancel()
|
|
return boxCli.RebootNow(ctx, true)
|
|
|
|
}
|
|
|
|
// UpdateOrionBox starts provisionning process for an Orion box (base or rover)
|
|
func (o *OrionService) UpdateOrionBox(r *http.Request,
|
|
args *UpdateOrionBoxArgs,
|
|
reply *UpdateOrionBoxResponse) error {
|
|
|
|
if err := o.setupMasterWifi(r.Context(), args.Box, args.Server); err != nil {
|
|
return err
|
|
}
|
|
|
|
return o.updateAndReboot(r.Context(), args.Box, args.Server, reply)
|
|
}
|
|
|
|
// NewServer returns a new configured JSON-RPC server
|
|
func NewServer() *rpc.Server {
|
|
server := rpc.NewServer()
|
|
server.RegisterCodec(json.NewCodec(), "application/json")
|
|
if err := server.RegisterService(NewOrionService(), "Orion"); err != nil {
|
|
panic(err)
|
|
}
|
|
return server
|
|
}
|