sige 1 year ago
parent
commit
6059e2bf30
  1. BIN
      app.db
  2. 6
      src/main/java/com/iflytop/uf/UfActuator.java
  3. 35
      src/main/java/com/iflytop/uf/UfActuatorBase.java
  4. 88
      src/main/java/com/iflytop/uf/UfActuatorCmdExecutor.java
  5. 10
      src/main/java/com/iflytop/uf/UfActuatorCommand.java
  6. 10
      src/main/java/com/iflytop/uf/UfActuatorHandler.java
  7. 1
      src/main/java/com/iflytop/uf/UfApplication.java
  8. 14
      src/main/java/com/iflytop/uf/UfApplicationRunner.java
  9. 59
      src/main/java/com/iflytop/uf/actuator/UfActStepperMotor.java
  10. 64
      src/main/java/com/iflytop/uf/connection/UfZcancmderWebsocket.java
  11. 2
      src/main/java/com/iflytop/uf/controller/UfApiActuator.java
  12. 3
      src/main/java/com/iflytop/uf/model/UfMdbActuator.java
  13. 10
      src/main/resources/application.yml
  14. 17
      src/main/resources/db/migration/V1_1__alter_table_actuators_add_column_type.sql
  15. 3
      src/main/resources/static/uf/css/app.5595db20.css
  16. 3
      src/main/resources/static/uf/css/app.eacba97d.css
  17. 2
      src/main/resources/static/uf/index.html
  18. 2
      src/main/resources/static/uf/js/app.66d72d84.js
  19. 1
      src/main/resources/static/uf/js/app.66d72d84.js.map
  20. 2
      src/main/resources/static/uf/js/app.98f78643.js
  21. 1
      src/main/resources/static/uf/js/app.98f78643.js.map

BIN
app.db

6
src/main/java/com/iflytop/uf/UfActuator.java

@ -0,0 +1,6 @@
package com.iflytop.uf;
import com.iflytop.uf.model.UfMdbActuatorCmd;
public interface UfActuator {
// execute
String execute( UfMdbActuatorCmd command );
}

35
src/main/java/com/iflytop/uf/UfActuatorBase.java

@ -0,0 +1,35 @@
package com.iflytop.uf;
import com.iflytop.uf.model.UfMdbActuatorCmd;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
abstract public class UfActuatorBase implements UfActuator {
// method map
private final Map<String, Method> methodMap;
// constructor
public UfActuatorBase() {
this.methodMap = new HashMap<>();
for ( var method : this.getClass().getMethods() ) {
var annotation = method.getAnnotation(UfActuatorCommand.class);
if ( annotation != null ) {
methodMap.put(annotation.name(), method);
}
}
}
@Override
public String execute(UfMdbActuatorCmd command) {
if ( !methodMap.containsKey(command.cmdKey) ) {
return UfActuatorCmdExecutor.getExecutor().sendCommandToConnection(command);
}
var method = methodMap.get(command.cmdKey);
try {
return (String)method.invoke(this, command);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}

88
src/main/java/com/iflytop/uf/UfActuatorCmdExecutor.java

@ -2,6 +2,7 @@ package com.iflytop.uf;
import com.iflytop.uf.model.UfMdbActuator;
import com.iflytop.uf.model.UfMdbActuatorCmd;
import com.iflytop.uf.model.UfMdbNotification;
import com.iflytop.uf.util.UfClassHelper;
import com.iflytop.uf.util.UfCommon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -10,29 +11,98 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
public class UfActuatorCmdExecutor {
// logger
public static final Logger LOG = LoggerFactory.getLogger(UfActuatorCmdExecutor.class);
// executor instance
private static UfActuatorCmdExecutor executor = null;
// actuator classes
private final Map<String, Class<?>> actuatorClasses;
// actuators
private final Map<String, UfActuator> actuators;
// emergency stop restore lock
private static final Object emergencyStopRestoreLock = new Object();
// setup
public static void setup() {
UfActuatorCmdExecutor.executor = new UfActuatorCmdExecutor();
UfActuatorCmdExecutor.executor.loadActuatorHandlers();
}
// execute command
public static String emergencyExecute(UfMdbActuatorCmd cmd) {
var con = UfApplication.getApp().connections.get(cmd.connectionKey);
return con.execute(cmd);
// get executor instance
public static UfActuatorCmdExecutor getExecutor() {
return UfActuatorCmdExecutor.executor;
}
// execute cmd
public static String execute(UfMdbActuatorCmd cmd) {
if ( UfActuatorCmdExecutor.hasEmergencyStopTriggered() ) {
throw new RuntimeException("触发紧急停止");
return UfActuatorCmdExecutor.executor.executeCommand(cmd);
}
// constructor
public UfActuatorCmdExecutor() {
this.actuators = new HashMap<>();
this.actuatorClasses = new HashMap<>();
}
// load actuator handlers
private void loadActuatorHandlers() {
// scan actuator handlers
var actuatorClassList = UfClassHelper.getAllClassesInPackage("com.iflytop.uf.actuator");
for ( var actuatorClass : actuatorClassList ) {
var annotation = actuatorClass.getAnnotation(UfActuatorHandler.class);
var name = annotation.name();
LOG.info("[Actuator Handler] handler : {}", name);
this.actuatorClasses.put(name, actuatorClass);
}
}
// execute command
public String executeCommand( UfMdbActuatorCmd cmd ) {
if ( this.actuators.containsKey(cmd.actuatorId) ) {
return this.actuators.get(cmd.actuatorId).execute(cmd);
}
var actuator = UfActiveRecord.findOne(UfMdbActuator.class, Map.of("id", cmd.actuatorId));
if ( null == actuator ) {
throw new RuntimeException("无效的设备ID :" + cmd.actuatorId);
}
var handlerClass = this.actuatorClasses.get(actuator.type);
if ( null == handlerClass ) {
// 如果没有找到对应的处理器则直接发送命令到连接
return this.sendCommandToConnection(cmd);
}
var handler = (UfActuator) UfClassHelper.newInstance(handlerClass);
this.actuators.put(cmd.actuatorId, handler);
return this.executeCommand(cmd);
}
// send command to connection to execute
public String sendCommandToConnection( UfMdbActuatorCmd cmd ) {
var con = UfApplication.getApp().connections.get(cmd.connectionKey);
return con.execute(cmd);
}
// emergency stop restore lock
private static final Object emergencyStopRestoreLock = new Object();
// execute command
public static String emergencyExecute(UfMdbActuatorCmd cmd) {
var con = UfApplication.getApp().connections.get(cmd.connectionKey);
return con.execute(cmd);
}
// execute cmd
public static String execute(String actuatorKey, String cmdKey, String cmdParams ) {
var actuator = UfActiveRecord.findOne(UfMdbActuator.class, Map.of("key", actuatorKey));

10
src/main/java/com/iflytop/uf/UfActuatorCommand.java

@ -0,0 +1,10 @@
package com.iflytop.uf;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface UfActuatorCommand {
String name();
}

10
src/main/java/com/iflytop/uf/UfActuatorHandler.java

@ -0,0 +1,10 @@
package com.iflytop.uf;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface UfActuatorHandler {
String name();
}

1
src/main/java/com/iflytop/uf/UfApplication.java

@ -4,6 +4,7 @@ import jakarta.annotation.Nonnull;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.springframework.beans.BeansException;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

14
src/main/java/com/iflytop/uf/UfApplicationRunner.java

@ -0,0 +1,14 @@
package com.iflytop.uf;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Order(1)
@Component
public class UfApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
UfApplication.getApp().connections.setup();
UfActuatorCmdExecutor.setup();
}
}

59
src/main/java/com/iflytop/uf/actuator/UfActStepperMotor.java

@ -0,0 +1,59 @@
package com.iflytop.uf.actuator;
import com.iflytop.uf.*;
import com.iflytop.uf.model.UfMdbActuator;
import com.iflytop.uf.model.UfMdbActuatorCmd;
@UfActuatorHandler(name = "stepper-motor")
public class UfActStepperMotor extends UfActuatorBase {
@UfActuatorCommand(name = "motor_easy_move_to_zero")
public String easyMoveToZero(UfMdbActuatorCmd cmd) {
var executor = UfActuatorCmdExecutor.getExecutor();
var result = executor.sendCommandToConnection(cmd);
// wait for finish
var waitCmd = new UfMdbActuatorCmd();
waitCmd.actuatorId = cmd.actuatorId;
waitCmd.cmdId = "0104";
waitCmd.cmdKey = "module_get_status";
waitCmd.cmdFlags = cmd.cmdFlags;
waitCmd.parameters = "";
waitCmd.connectionKey = cmd.connectionKey;
do {
String statusText = executor.sendCommandToConnection(waitCmd);
int status = Integer.parseInt(statusText);
if ( 0 == status ) {
break;
}
} while ( true );
return result;
}
//
// // cmd motor easy move to
// public void cmdMotorEasyMoveTo(UfMdbActuatorCmd command) {
// this.executeDeviceCommand(command);
// this.waitForActuatorFinish(command);
//
// var actuator = UfActiveRecord.findOne(UfMdbActuator.class, command.actuatorId);
// var encoderAvailable = actuator.getProperty("encoderAvailable");
// if ( null == encoderAvailable || !encoderAvailable.asBoolean() ) {
// return ;
// }
//
// var destValue = Integer.parseInt(command.parameters);
// var encoderValue = this.getActuatorEncoderValue(command);
// if ( Math.abs(encoderValue - destValue) > 10 ) {
// throw new RuntimeException(String.format("电机 [%s] 移动失败,目标位置:%d,当前位置:%d", actuator.name, destValue, encoderValue));
// }
// }
//
// // cmd motor read enc val
// private Integer getActuatorEncoderValue(UfMdbActuatorCmd srcCmd) {
// var command = new UfMdbActuatorCmd();
// command.actuatorId = srcCmd.actuatorId;
// command.cmdId = "0219";
// command.cmdKey = "motor_read_enc_val";
// command.cmdFlags = srcCmd.cmdFlags;
// String value = this.executeDeviceCommand(command);
// return Integer.parseInt(value);
// }
}

64
src/main/java/com/iflytop/uf/connection/UfZcancmderWebsocket.java

@ -93,10 +93,6 @@ public class UfZcancmderWebsocket extends UfConnectionBase {
} catch (Exception e) {
throw new RuntimeException(e);
}
if ( 1 == command.waitForFinish ) {
this.waitForActuatorFinish(command);
}
return returnValue;
}
@ -307,64 +303,4 @@ public class UfZcancmderWebsocket extends UfConnectionBase {
this.callLock.notifyAll();
}
}
// wait for actuator finish
private void waitForActuatorFinish(UfMdbActuatorCmd actuatorCmd) {
do {
String filePath = UfApplication.getApp().getEnv().getProperty("app.emergency-stop-file-path");
if ( null != filePath ) {
try { // read all content from file as string
String state = Files.readString(Path.of(filePath));
if ( "0".equals(state.trim()) ) {
return ; // emergency stop triggered
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
var waitCmd = new UfMdbActuatorCmd();
waitCmd.actuatorId = actuatorCmd.actuatorId;
waitCmd.cmdId = "0104";
waitCmd.cmdKey = "module_get_status";
waitCmd.cmdFlags = actuatorCmd.cmdFlags;
waitCmd.parameters = "";
String statusText = this.executeDeviceCommand(waitCmd);
int status = Integer.parseInt(statusText);
if ( 0 == status ) {
break;
}
UfCommon.delay(100);
} while ( true );
}
// cmd motor easy move to
public void cmdMotorEasyMoveTo(UfMdbActuatorCmd command) {
this.executeDeviceCommand(command);
this.waitForActuatorFinish(command);
var actuator = UfActiveRecord.findOne(UfMdbActuator.class, command.actuatorId);
var encoderAvailable = actuator.getProperty("encoderAvailable");
if ( null == encoderAvailable || !encoderAvailable.asBoolean() ) {
return ;
}
var destValue = Integer.parseInt(command.parameters);
var encoderValue = this.getActuatorEncoderValue(command);
if ( Math.abs(encoderValue - destValue) > 10 ) {
throw new RuntimeException(String.format("电机 [%s] 移动失败,目标位置:%d,当前位置:%d", actuator.name, destValue, encoderValue));
}
}
// cmd motor read enc val
private Integer getActuatorEncoderValue(UfMdbActuatorCmd srcCmd) {
var command = new UfMdbActuatorCmd();
command.actuatorId = srcCmd.actuatorId;
command.cmdId = "0219";
command.cmdKey = "motor_read_enc_val";
command.cmdFlags = srcCmd.cmdFlags;
String value = this.executeDeviceCommand(command);
return Integer.parseInt(value);
}
}

2
src/main/java/com/iflytop/uf/controller/UfApiActuator.java

@ -27,7 +27,7 @@ public class UfApiActuator extends UfApiControllerBase {
actuator.isNewRecord = false;
}
actuator.save();
return success();
return success(actuator);
}
@PostMapping("/api/actuator/delete")

3
src/main/java/com/iflytop/uf/model/UfMdbActuator.java

@ -18,6 +18,9 @@ public class UfMdbActuator extends UfActiveRecord {
public String name;
@UfActiveRecordField
public String type;
@UfActiveRecordField
public String properties = "{}";
// get table name

10
src/main/resources/application.yml

@ -13,4 +13,12 @@ flyway:
encoding: UTF-8
locations: classpath:db/migration
validate-on-migrate: true
validateMigrationNaming: true
validateMigrationNaming: true
device :
connections:
- name : zcancmder
key : zcancmder
enable : true
type : ZcancmderWebsocket
uri: ws://127.0.0.1:19005

17
src/main/resources/db/migration/V1_1__alter_table_actuators_add_column_type.sql

@ -0,0 +1,17 @@
-- rename table to backup
ALTER TABLE app_actuators RENAME TO app_actuators_bk_202405061735;
-- create new table with new columns
CREATE TABLE app_actuators (
"id" text NOT NULL,
"moduleId" text NOT NULL,
"key" TEXT NOT NULL,
"name" TEXT NOT NULL,
"type" TEXT NOT NULL,
"properties" TEXT NOT NULL DEFAULT "{}",
PRIMARY KEY ("id")
);
-- copy data from backup table to new table
INSERT INTO app_actuators (id, moduleId, key, name, type, properties)
SELECT id, moduleId, key, name, '', properties FROM app_actuators_bk_202405061735;

3
src/main/resources/static/uf/css/app.5595db20.css
File diff suppressed because it is too large
View File

3
src/main/resources/static/uf/css/app.eacba97d.css
File diff suppressed because it is too large
View File

2
src/main/resources/static/uf/index.html

@ -1 +1 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/uf/favicon.ico"><title>web</title><script defer="defer" src="/uf/js/chunk-vendors.0f638fda.js"></script><script defer="defer" src="/uf/js/app.66d72d84.js"></script><link href="/uf/css/app.eacba97d.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/uf/favicon.ico"><title>web</title><script defer="defer" src="/uf/js/chunk-vendors.0f638fda.js"></script><script defer="defer" src="/uf/js/app.98f78643.js"></script><link href="/uf/css/app.5595db20.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

2
src/main/resources/static/uf/js/app.66d72d84.js
File diff suppressed because it is too large
View File

1
src/main/resources/static/uf/js/app.66d72d84.js.map
File diff suppressed because it is too large
View File

2
src/main/resources/static/uf/js/app.98f78643.js
File diff suppressed because it is too large
View File

1
src/main/resources/static/uf/js/app.98f78643.js.map
File diff suppressed because it is too large
View File

Loading…
Cancel
Save