orion/cmd/server/jsonrpc/rpc.go

526 lines
14 KiB
Go

package jsonrpc
import (
"context"
"fmt"
"log"
"net/http"
"time"
"forge.cadoles.com/Cadoles/owrt"
"forge.cadoles.com/Pyxis/orion/emlid"
"forge.cadoles.com/Pyxis/orion/emlid/reachview"
"forge.cadoles.com/Pyxis/orion/emlid/updater"
"github.com/gorilla/rpc"
"github.com/gorilla/rpc/json"
"github.com/pkg/errors"
)
// OrionService is the JSON-RPC API
type OrionService struct {
UCI *owrt.UCI
}
// NewOrionService create a new OrionService !
func NewOrionService() *OrionService {
uci := owrt.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 map[int]*owrt.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 {
Cleanup bool
Iface *owrt.UCIWirelessInterface
}
// CreateIfaceResponse argument structure for exported method OwrtCreateWifiInterface
type CreateIfaceResponse struct {
Errors []*owrt.Action
Iface *owrt.UCIWirelessInterface
}
// 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 *owrt.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 *owrt.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 := owrt.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(rqContext context.Context) ([]emlid.Service, error) {
ctx, cancelDiscover := context.WithTimeout(rqContext, 55*time.Second)
defer cancelDiscover()
return emlid.Discover(ctx)
}
func (o *OrionService) connectUpdater(rqContext context.Context, box *OrionBox) (*updater.Client, error) {
var boxCli *updater.Client
service, err := o.discoverService(rqContext)
fmt.Printf("DEBUG len = %d\n", len(service))
if len(service) == 0 {
fmt.Println("DEBUG Connecting to box with EndPoint !")
boxCli = updater.NewClient(
emlid.WithEndpoint(box.Address, 80),
)
} else if err == nil {
fmt.Println("DEBUG Connecting to box with Service !")
boxCli = updater.NewClient(
emlid.WithService(service[0]),
)
} else {
fmt.Printf("Non mais là c'est vraiment étrange !")
return nil, err
}
//fmt.Printf("NAME: %s, IP: %s", service[0].Name, service[0].AddrV4)
return boxCli, err
}
// 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)
}
boxCli, cliErr := o.connectUpdater(rqContext, box)
if cliErr != nil {
return errors.Wrap(cliErr, "Creation of updater client failed")
}
if err := boxCli.Connect(); err != nil {
return errors.Wrap(err, "Connecting to Box failed")
}
defer boxCli.Close()
fmt.Println("Running Tests !")
ctx, testResultsCancel := context.WithTimeout(rqContext, 55*time.Second)
defer testResultsCancel()
_, err := boxCli.TestResults(ctx)
if err != nil {
return errors.Wrap(err, "Minimal test failed")
}
fmt.Println("Add Wifi !")
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()
fmt.Println("Join Wifi !")
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 address and run basic tests on version dans updates
func (o *OrionService) updateAndReboot(rqContext context.Context, box *OrionBox, reply *UpdateOrionBoxResponse) error {
time.Sleep(3 * time.Second)
boxCli, err := o.connectUpdater(rqContext, box)
if err != nil {
return errors.Wrap(err, "Problem connecting to updater")
}
if err := boxCli.Connect(); err != nil {
fmt.Println("Waiting 3 seconds on error")
time.Sleep(3 * time.Second)
boxCli, clerr := o.connectUpdater(rqContext, box)
if clerr != nil {
return errors.Wrap(err, "Problem connecting to updater")
}
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
// }
_ = o.setupMasterWifi(r.Context(), args.Box, args.Server)
return o.updateAndReboot(r.Context(), args.Box, reply)
}
// connectReachview creates the client to a reachview module and returns it !
func (o *OrionService) connectReachView(rqContext context.Context, box *OrionBox) (*reachview.Client, error) {
var boxCli *reachview.Client
service, err := o.discoverService(rqContext)
fmt.Printf("DEBUG len = %d\n", len(service))
if len(service) == 0 {
fmt.Println("DEBUG Connecting to box with EndPoint !")
boxCli = reachview.NewClient(
emlid.WithEndpoint(box.Address, 80),
)
} else if err == nil {
fmt.Println("DEBUG Connecting to box with Service !")
boxCli = reachview.NewClient(
emlid.WithService(service[0]),
)
} else {
fmt.Printf("Non mais là c'est vraiment étrange !")
return nil, err
}
//fmt.Printf("NAME: %s, IP: %s", service[0].Name, service[0].AddrV4)
return boxCli, err
}
// ConfigOrionBoxArgs argument structure for exported method OwrtCreateWifiInterface
type ConfigOrionBoxArgs struct {
Box *OrionBox
}
// ConfigOrionBoxResponse argument structure for exported method OwrtCreateWifiInterface
type ConfigOrionBoxResponse struct {
}
func (o *OrionService) resetConfiguration(rqContext context.Context, c *reachview.Client) {
ctx, resetCancel := context.WithTimeout(rqContext, 55*time.Second)
defer resetCancel()
result, _, err := c.ResetConfiguration(ctx)
if err != nil {
log.Fatal(err)
}
if result != reachview.ConfigurationApplySuccess {
log.Fatal("configuration reset failed !")
}
}
// FIXME have to came from parameters !
func (o *OrionService) getCommonConfiguration() *reachview.Configuration {
return &reachview.Configuration{
RTKSettings: &reachview.RTKSettings{
PositioningSystems: &reachview.PositionningSystems{
GPS: reachview.True,
GLONASS: reachview.True,
Galileo: reachview.True,
SBAS: reachview.True,
QZSS: reachview.True,
},
UpdateRate: reachview.String("5"),
},
LoRa: &reachview.LoRa{
AirRate: reachview.String("9.11"),
Frequency: reachview.Float(868000),
OutputPower: reachview.String("20"),
},
PositionOutput: &reachview.PositionOutput{
Output1: &reachview.Output{
Enabled: reachview.False,
},
Output2: &reachview.Output{
Enabled: reachview.False,
},
},
}
}
func (o *OrionService) applyConfiguration(rqContext context.Context, c *reachview.Client, config *reachview.Configuration) error {
ctx, applyConfCancel := context.WithTimeout(rqContext, 55*time.Second)
defer applyConfCancel()
result, _, err := c.ApplyConfiguration(ctx, config)
if err != nil {
return err
}
if result != reachview.ConfigurationApplySuccess {
return fmt.Errorf("Configuration update failed")
}
return c.RestartRTKLib()
}
func (o *OrionService) configureRover(rqContext context.Context, c *reachview.Client) error {
_ = c.Connect()
defer c.Close()
o.resetConfiguration(rqContext, c)
config := o.getCommonConfiguration()
config.RTKSettings.GPSARMode = reachview.GPSARModeFixAndHold
config.RTKSettings.GLONASSARMode = reachview.On
config.RTKSettings.PositionningMode = reachview.PositionningModeKinematic
config.RTKSettings.UpdateRate = reachview.String("5")
config.CorrectionInput = &reachview.CorrectionInput{
Input2: &reachview.Input2{
Input: reachview.Input{
Enabled: reachview.True,
Format: reachview.IOFormatRTCM3,
Type: reachview.IOTypeLoRa,
Path: reachview.String("lora"),
},
SendPositionToBase: reachview.Off,
},
}
config.BaseMode = &reachview.BaseMode{
Output: &reachview.Output{
Enabled: reachview.False,
},
}
log.Println("configuring module as rover")
return o.applyConfiguration(rqContext, c, config)
}
// ConfigureOrionBox starts provisionning process for an Orion box (base or rover)
func (o *OrionService) ConfigureOrionBox(r *http.Request,
args *ConfigOrionBoxArgs,
reply *ConfigOrionBoxResponse) error {
reach, err := o.connectReachView(r.Context(), args.Box)
if err != nil {
return errors.Wrap(err, "Impossible to create ReachView client")
}
fmt.Println("Start Rover Configuraiton !")
return o.configureRover(r.Context(), reach)
}
// 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
}