software copyright

This commit is contained in:
jiahong
2026-03-22 15:24:40 +08:00
parent e303bb868a
commit 60f336e345
155 changed files with 127262 additions and 0 deletions
@@ -0,0 +1,219 @@
/*
* 自然写互动课堂应用开发SDK软件 V1.0
* PenDevice - 点阵笔设备数据模型
*
* 描述:封装点阵笔的设备信息、连接状态、能力参数等
*/
package com.writech.sdk.model;
import java.io.Serializable;
/**
* 点阵笔设备模型
* 包含设备基本信息、硬件参数和连接状态
*/
public class PenDevice implements Serializable {
private static final long serialVersionUID = 1L;
/* ========== 基本信息 ========== */
/** 设备MAC地址(唯一标识) */
private String macAddress;
/** 设备名称(用户可自定义) */
private String deviceName;
/** 设备型号(如 WP-200, WP-300 */
private String modelName;
/** 固件版本号(如 2.1.5 */
private String firmwareVersion;
/** 硬件版本号 */
private String hardwareVersion;
/** 设备序列号 */
private String serialNumber;
/* ========== 硬件能力 ========== */
/** 采样率(Hz,常见值:100, 200 */
private int sampleRate;
/** 压力感应级别(常见值:1024, 2048, 4096 */
private int pressureLevels;
/** 坐标分辨率(DPI,常见值:300, 600 */
private int coordinateDpi;
/** 是否支持倾斜角检测 */
private boolean tiltSupported;
/** BLE协议版本(4.2 / 5.0 / 5.3 */
private String bleVersion;
/** 电池容量(mAh */
private int batteryCapacity;
/* ========== 运行状态 ========== */
/** 连接状态枚举 */
public enum ConnectionState {
DISCONNECTED, /* 未连接 */
CONNECTING, /* 正在连接 */
CONNECTED, /* 已连接 */
RECONNECTING /* 正在重连 */
}
/** 当前连接状态 */
private ConnectionState connectionState = ConnectionState.DISCONNECTED;
/** 当前电量百分比(0-100 */
private int batteryLevel;
/** 是否正在充电 */
private boolean isCharging;
/** 是否正在书写(笔尖接触纸面) */
private boolean isWriting;
/** 信号强度RSSIdBm */
private int rssi;
/** 最后一次通信时间(毫秒时间戳) */
private long lastCommunicationTime;
/** 累计书写时长(秒) */
private long totalWritingDuration;
/** 绑定的学生ID */
private String boundStudentId;
/** 绑定的学生姓名 */
private String boundStudentName;
/* ========== 构造函数 ========== */
public PenDevice() {
}
public PenDevice(String macAddress, String deviceName) {
this.macAddress = macAddress;
this.deviceName = deviceName;
this.sampleRate = 100;
this.pressureLevels = 4096;
this.coordinateDpi = 300;
}
/* ========== Getter / Setter ========== */
public String getMacAddress() { return macAddress; }
public void setMacAddress(String macAddress) { this.macAddress = macAddress; }
public String getDeviceName() { return deviceName; }
public void setDeviceName(String deviceName) { this.deviceName = deviceName; }
public String getModelName() { return modelName; }
public void setModelName(String modelName) { this.modelName = modelName; }
public String getFirmwareVersion() { return firmwareVersion; }
public void setFirmwareVersion(String firmwareVersion) { this.firmwareVersion = firmwareVersion; }
public String getHardwareVersion() { return hardwareVersion; }
public void setHardwareVersion(String v) { this.hardwareVersion = v; }
public String getSerialNumber() { return serialNumber; }
public void setSerialNumber(String serialNumber) { this.serialNumber = serialNumber; }
public int getSampleRate() { return sampleRate; }
public void setSampleRate(int sampleRate) { this.sampleRate = sampleRate; }
public int getPressureLevels() { return pressureLevels; }
public void setPressureLevels(int pressureLevels) { this.pressureLevels = pressureLevels; }
public int getCoordinateDpi() { return coordinateDpi; }
public void setCoordinateDpi(int coordinateDpi) { this.coordinateDpi = coordinateDpi; }
public boolean isTiltSupported() { return tiltSupported; }
public void setTiltSupported(boolean tiltSupported) { this.tiltSupported = tiltSupported; }
public String getBleVersion() { return bleVersion; }
public void setBleVersion(String bleVersion) { this.bleVersion = bleVersion; }
public int getBatteryCapacity() { return batteryCapacity; }
public void setBatteryCapacity(int batteryCapacity) { this.batteryCapacity = batteryCapacity; }
public ConnectionState getConnectionState() { return connectionState; }
public void setConnectionState(ConnectionState state) { this.connectionState = state; }
public int getBatteryLevel() { return batteryLevel; }
public void setBatteryLevel(int batteryLevel) { this.batteryLevel = batteryLevel; }
public boolean isCharging() { return isCharging; }
public void setCharging(boolean charging) { isCharging = charging; }
public boolean isWriting() { return isWriting; }
public void setWriting(boolean writing) { isWriting = writing; }
public int getRssi() { return rssi; }
public void setRssi(int rssi) { this.rssi = rssi; }
public long getLastCommunicationTime() { return lastCommunicationTime; }
public void setLastCommunicationTime(long t) { this.lastCommunicationTime = t; }
public long getTotalWritingDuration() { return totalWritingDuration; }
public void setTotalWritingDuration(long d) { this.totalWritingDuration = d; }
public String getBoundStudentId() { return boundStudentId; }
public void setBoundStudentId(String id) { this.boundStudentId = id; }
public String getBoundStudentName() { return boundStudentName; }
public void setBoundStudentName(String name) { this.boundStudentName = name; }
/* ========== 便捷方法 ========== */
/** 是否已连接 */
public boolean isConnected() {
return connectionState == ConnectionState.CONNECTED;
}
/** 电量是否低(<= 10% */
public boolean isLowBattery() {
return batteryLevel <= 10 && !isCharging;
}
/** 获取设备显示名称(优先显示学生姓名) */
public String getDisplayName() {
if (boundStudentName != null && !boundStudentName.isEmpty()) {
return boundStudentName + "的笔";
}
return deviceName != null ? deviceName : "WritechPen-" + macAddress;
}
@Override
public String toString() {
return "PenDevice{" +
"mac='" + macAddress + '\'' +
", name='" + deviceName + '\'' +
", model='" + modelName + '\'' +
", state=" + connectionState +
", battery=" + batteryLevel + "%" +
", writing=" + isWriting +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PenDevice that = (PenDevice) o;
return macAddress != null && macAddress.equals(that.macAddress);
}
@Override
public int hashCode() {
return macAddress != null ? macAddress.hashCode() : 0;
}
}
@@ -0,0 +1,306 @@
/*
* 自然写互动课堂应用开发SDK软件 V1.0
* RecognitionResult - 识别结果数据模型
*
* 描述:封装OCR识别、数学公式识别、笔顺评分等各类识别结果
*/
package com.writech.sdk.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 识别结果统一模型
* 支持多种识别类型的结果封装
*/
public class RecognitionResult implements Serializable {
private static final long serialVersionUID = 1L;
/* ========== 识别类型常量 ========== */
/** 手写文字识别 */
public static final int TYPE_HANDWRITING = 0;
/** 数学公式识别 */
public static final int TYPE_MATH = 1;
/** 笔顺评分 */
public static final int TYPE_STROKE_ORDER = 2;
/** 作文评分 */
public static final int TYPE_ESSAY = 3;
/* ========== 候选结果内部类 ========== */
/** 单个候选识别结果 */
public static class Candidate implements Serializable {
private static final long serialVersionUID = 1L;
/** 识别文本 */
public String text;
/** 置信度(0.0~1.0 */
public float confidence;
/** 候选排名 */
public int rank;
public Candidate() {}
public Candidate(String text, float confidence, int rank) {
this.text = text;
this.confidence = confidence;
this.rank = rank;
}
@Override
public String toString() {
return "Candidate{'" + text + "', conf=" + confidence + "}";
}
}
/** 笔顺评分详情 */
public static class StrokeOrderDetail implements Serializable {
private static final long serialVersionUID = 1L;
/** 评分笔画序号 */
public int strokeIndex;
/** 该笔是否正确 */
public boolean isCorrect;
/** 标准笔顺名称(如"横"、"竖"、"撇" */
public String standardStrokeName;
/** 实际书写的笔画类型 */
public String actualStrokeName;
/** 笔画形态相似度(0.0~1.0 */
public float shapeSimilarity;
public StrokeOrderDetail() {}
@Override
public String toString() {
return "Stroke#" + strokeIndex + ": " + (isCorrect ? "正确" : "错误")
+ " (标准:" + standardStrokeName + ", 实际:" + actualStrokeName + ")";
}
}
/** 作文评分详情 */
public static class EssayScoreDetail implements Serializable {
private static final long serialVersionUID = 1L;
/** 内容分(百分制) */
public float contentScore;
/** 结构分 */
public float structureScore;
/** 语言分 */
public float languageScore;
/** 书写规范分 */
public float handwritingScore;
/** 总分 */
public float totalScore;
/** 评语 */
public String comment;
/** 优点列表 */
public List<String> highlights = new ArrayList<>();
/** 改进建议列表 */
public List<String> suggestions = new ArrayList<>();
public EssayScoreDetail() {}
}
/* ========== 结果字段 ========== */
/** 识别请求ID(对应任务ID) */
private int requestId;
/** 识别类型 */
private int recognitionType;
/** 识别是否成功 */
private boolean success;
/** 错误码(成功时为0 */
private int errorCode;
/** 错误消息 */
private String errorMessage;
/** 主要识别结果文本 */
private String resultText;
/** 主要结果置信度 */
private float confidence;
/** 候选结果列表(按置信度降序) */
private List<Candidate> candidates;
/** 笔顺评分详情(仅笔顺类型) */
private List<StrokeOrderDetail> strokeOrderDetails;
/** 笔顺总分(0-100 */
private float strokeOrderScore;
/** 笔顺正确笔画数 */
private int correctStrokeCount;
/** 笔顺总笔画数 */
private int totalStrokeCount;
/** 作文评分详情(仅作文类型) */
private EssayScoreDetail essayDetail;
/** 数学公式LaTeX表示(仅数学类型) */
private String mathLatex;
/** 数学计算结果(如果是计算题) */
private String mathAnswer;
/** 识别耗时(毫秒) */
private long processingTimeMs;
/** 结果来源("online"在线 / "offline"离线 / "cache"缓存) */
private String source;
/** 识别时间戳 */
private long timestamp;
/* ========== 构造函数 ========== */
public RecognitionResult() {
this.candidates = new ArrayList<>();
this.strokeOrderDetails = new ArrayList<>();
this.timestamp = System.currentTimeMillis();
}
/** 创建成功结果 */
public static RecognitionResult success(int requestId, int type, String text, float confidence) {
RecognitionResult result = new RecognitionResult();
result.requestId = requestId;
result.recognitionType = type;
result.success = true;
result.errorCode = 0;
result.resultText = text;
result.confidence = confidence;
return result;
}
/** 创建失败结果 */
public static RecognitionResult failure(int requestId, int errorCode, String message) {
RecognitionResult result = new RecognitionResult();
result.requestId = requestId;
result.success = false;
result.errorCode = errorCode;
result.errorMessage = message;
return result;
}
/* ========== Getter / Setter ========== */
public int getRequestId() { return requestId; }
public void setRequestId(int id) { this.requestId = id; }
public int getRecognitionType() { return recognitionType; }
public void setRecognitionType(int type) { this.recognitionType = type; }
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public int getErrorCode() { return errorCode; }
public void setErrorCode(int code) { this.errorCode = code; }
public String getErrorMessage() { return errorMessage; }
public void setErrorMessage(String msg) { this.errorMessage = msg; }
public String getResultText() { return resultText; }
public void setResultText(String text) { this.resultText = text; }
public float getConfidence() { return confidence; }
public void setConfidence(float c) { this.confidence = c; }
public List<Candidate> getCandidates() { return candidates; }
public void setCandidates(List<Candidate> c) { this.candidates = c; }
public void addCandidate(String text, float confidence, int rank) {
candidates.add(new Candidate(text, confidence, rank));
}
public List<StrokeOrderDetail> getStrokeOrderDetails() { return strokeOrderDetails; }
public void setStrokeOrderDetails(List<StrokeOrderDetail> d) { this.strokeOrderDetails = d; }
public float getStrokeOrderScore() { return strokeOrderScore; }
public void setStrokeOrderScore(float s) { this.strokeOrderScore = s; }
public int getCorrectStrokeCount() { return correctStrokeCount; }
public void setCorrectStrokeCount(int c) { this.correctStrokeCount = c; }
public int getTotalStrokeCount() { return totalStrokeCount; }
public void setTotalStrokeCount(int t) { this.totalStrokeCount = t; }
public EssayScoreDetail getEssayDetail() { return essayDetail; }
public void setEssayDetail(EssayScoreDetail d) { this.essayDetail = d; }
public String getMathLatex() { return mathLatex; }
public void setMathLatex(String latex) { this.mathLatex = latex; }
public String getMathAnswer() { return mathAnswer; }
public void setMathAnswer(String answer) { this.mathAnswer = answer; }
public long getProcessingTimeMs() { return processingTimeMs; }
public void setProcessingTimeMs(long ms) { this.processingTimeMs = ms; }
public String getSource() { return source; }
public void setSource(String source) { this.source = source; }
public long getTimestamp() { return timestamp; }
public void setTimestamp(long t) { this.timestamp = t; }
/* ========== 便捷方法 ========== */
/** 获取最佳候选结果 */
public Candidate getBestCandidate() {
return candidates.isEmpty() ? null : candidates.get(0);
}
/** 获取笔顺正确率 */
public float getStrokeOrderAccuracy() {
return totalStrokeCount > 0 ? (float) correctStrokeCount / totalStrokeCount : 0;
}
/** 获取识别类型的中文描述 */
public String getTypeDescription() {
switch (recognitionType) {
case TYPE_HANDWRITING: return "手写识别";
case TYPE_MATH: return "数学识别";
case TYPE_STROKE_ORDER: return "笔顺评分";
case TYPE_ESSAY: return "作文评分";
default: return "未知类型";
}
}
@Override
public String toString() {
if (success) {
return "RecognitionResult{type=" + getTypeDescription()
+ ", text='" + resultText + "'"
+ ", confidence=" + confidence
+ ", source=" + source
+ ", time=" + processingTimeMs + "ms}";
} else {
return "RecognitionResult{FAILED, code=" + errorCode
+ ", msg='" + errorMessage + "'}";
}
}
}
@@ -0,0 +1,304 @@
/*
* 自然写互动课堂应用开发SDK软件 V1.0
* StrokePath - 笔迹路径数据模型
*
* 描述:封装一条完整笔画的坐标序列、属性和元数据
*/
package com.writech.sdk.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 笔迹路径模型
* 代表从落笔到抬笔的一条完整笔画数据
*/
public class StrokePath implements Serializable {
private static final long serialVersionUID = 1L;
/* ========== 采样点内部类 ========== */
/** 单个笔迹采样点 */
public static class Point implements Serializable {
private static final long serialVersionUID = 1L;
/** X坐标(屏幕像素或物理mm,取决于坐标空间) */
public float x;
/** Y坐标 */
public float y;
/** 压力值(归一化 0.0~1.0 */
public float pressure;
/** 时间戳(相对于笔画开始时间的毫秒偏移) */
public long timeOffset;
/** 笔尖倾斜角度(度,0-90,0为垂直,部分笔支持) */
public float tiltAngle;
/** 笔尖方位角(度,0-360,部分笔支持) */
public float azimuthAngle;
public Point() {}
public Point(float x, float y, float pressure, long timeOffset) {
this.x = x;
this.y = y;
this.pressure = pressure;
this.timeOffset = timeOffset;
}
@Override
public String toString() {
return "(" + x + "," + y + ",p=" + pressure + ",t=" + timeOffset + ")";
}
}
/* ========== 笔画属性 ========== */
/** 笔画唯一ID */
private String strokeId;
/** 来源笔设备MAC地址 */
private String penMac;
/** 学生ID */
private String studentId;
/** 页面ID(标识书写所在页面) */
private String pageId;
/** 笔画开始时间(绝对时间戳毫秒) */
private long startTimestamp;
/** 笔画结束时间 */
private long endTimestamp;
/** 笔画颜色(ARGB */
private int color = 0xFF000000;
/** 笔画基础线宽(像素) */
private float baseWidth = 3.0f;
/** 采样点列表 */
private List<Point> points;
/* ========== 分析结果(由OCR/AI引擎填充) ========== */
/** 识别的文字内容 */
private String recognizedText;
/** 识别置信度 */
private float recognitionConfidence;
/** 笔顺序号(在整个书写序列中的顺序) */
private int strokeOrder;
/** 是否为有效笔画(排除误触等) */
private boolean isValid = true;
/* ========== 构造函数 ========== */
public StrokePath() {
this.points = new ArrayList<>();
}
public StrokePath(String strokeId, String penMac) {
this.strokeId = strokeId;
this.penMac = penMac;
this.points = new ArrayList<>();
this.startTimestamp = System.currentTimeMillis();
}
/* ========== 点操作方法 ========== */
/** 添加采样点 */
public void addPoint(float x, float y, float pressure, long timeOffset) {
points.add(new Point(x, y, pressure, timeOffset));
}
/** 添加采样点(含倾斜角) */
public void addPointWithTilt(float x, float y, float pressure,
long timeOffset, float tilt, float azimuth) {
Point p = new Point(x, y, pressure, timeOffset);
p.tiltAngle = tilt;
p.azimuthAngle = azimuth;
points.add(p);
}
/** 获取采样点数量 */
public int getPointCount() {
return points.size();
}
/** 获取指定索引的采样点 */
public Point getPoint(int index) {
if (index >= 0 && index < points.size()) {
return points.get(index);
}
return null;
}
/** 获取所有采样点 */
public List<Point> getPoints() {
return points;
}
/* ========== 笔画几何计算 ========== */
/** 计算笔画总长度(像素) */
public float calculateLength() {
float length = 0;
for (int i = 1; i < points.size(); i++) {
Point p0 = points.get(i - 1);
Point p1 = points.get(i);
float dx = p1.x - p0.x;
float dy = p1.y - p0.y;
length += (float) Math.sqrt(dx * dx + dy * dy);
}
return length;
}
/** 计算笔画包围盒 */
public float[] getBoundingBox() {
if (points.isEmpty()) return new float[]{0, 0, 0, 0};
float minX = Float.MAX_VALUE, minY = Float.MAX_VALUE;
float maxX = Float.MIN_VALUE, maxY = Float.MIN_VALUE;
for (Point p : points) {
if (p.x < minX) minX = p.x;
if (p.y < minY) minY = p.y;
if (p.x > maxX) maxX = p.x;
if (p.y > maxY) maxY = p.y;
}
return new float[]{minX, minY, maxX, maxY};
}
/** 计算平均书写速度(像素/毫秒) */
public float calculateAverageSpeed() {
if (points.size() < 2) return 0;
float totalLength = calculateLength();
long duration = points.get(points.size() - 1).timeOffset - points.get(0).timeOffset;
return duration > 0 ? totalLength / duration : 0;
}
/** 计算平均压力 */
public float calculateAveragePressure() {
if (points.isEmpty()) return 0;
float sum = 0;
for (Point p : points) {
sum += p.pressure;
}
return sum / points.size();
}
/** 获取书写持续时间(毫秒) */
public long getDuration() {
if (points.size() < 2) return 0;
return points.get(points.size() - 1).timeOffset - points.get(0).timeOffset;
}
/* ========== 序列化方法 ========== */
/**
* 将笔画数据序列化为紧凑的二进制格式
* 用于BLE传输和本地缓存
*
* 格式:
* [4字节 点数][每个点: 4字节x + 4字节y + 2字节pressure + 4字节timeOffset]
*/
public byte[] toBytes() {
int pointCount = points.size();
byte[] data = new byte[4 + pointCount * 14];
/* 写入点数(大端序) */
data[0] = (byte) ((pointCount >> 24) & 0xFF);
data[1] = (byte) ((pointCount >> 16) & 0xFF);
data[2] = (byte) ((pointCount >> 8) & 0xFF);
data[3] = (byte) (pointCount & 0xFF);
int offset = 4;
for (Point p : points) {
/* 写入X坐标(float → 4字节) */
int fx = Float.floatToIntBits(p.x);
data[offset++] = (byte) ((fx >> 24) & 0xFF);
data[offset++] = (byte) ((fx >> 16) & 0xFF);
data[offset++] = (byte) ((fx >> 8) & 0xFF);
data[offset++] = (byte) (fx & 0xFF);
/* 写入Y坐标 */
int fy = Float.floatToIntBits(p.y);
data[offset++] = (byte) ((fy >> 24) & 0xFF);
data[offset++] = (byte) ((fy >> 16) & 0xFF);
data[offset++] = (byte) ((fy >> 8) & 0xFF);
data[offset++] = (byte) (fy & 0xFF);
/* 写入压力值(归一化后*65535转uint16 */
int pressure16 = (int) (p.pressure * 65535);
data[offset++] = (byte) ((pressure16 >> 8) & 0xFF);
data[offset++] = (byte) (pressure16 & 0xFF);
/* 写入时间偏移(uint32 */
long t = p.timeOffset;
data[offset++] = (byte) ((t >> 24) & 0xFF);
data[offset++] = (byte) ((t >> 16) & 0xFF);
data[offset++] = (byte) ((t >> 8) & 0xFF);
data[offset++] = (byte) (t & 0xFF);
}
return data;
}
/* ========== Getter / Setter ========== */
public String getStrokeId() { return strokeId; }
public void setStrokeId(String strokeId) { this.strokeId = strokeId; }
public String getPenMac() { return penMac; }
public void setPenMac(String penMac) { this.penMac = penMac; }
public String getStudentId() { return studentId; }
public void setStudentId(String studentId) { this.studentId = studentId; }
public String getPageId() { return pageId; }
public void setPageId(String pageId) { this.pageId = pageId; }
public long getStartTimestamp() { return startTimestamp; }
public void setStartTimestamp(long t) { this.startTimestamp = t; }
public long getEndTimestamp() { return endTimestamp; }
public void setEndTimestamp(long t) { this.endTimestamp = t; }
public int getColor() { return color; }
public void setColor(int color) { this.color = color; }
public float getBaseWidth() { return baseWidth; }
public void setBaseWidth(float w) { this.baseWidth = w; }
public String getRecognizedText() { return recognizedText; }
public void setRecognizedText(String text) { this.recognizedText = text; }
public float getRecognitionConfidence() { return recognitionConfidence; }
public void setRecognitionConfidence(float c) { this.recognitionConfidence = c; }
public int getStrokeOrder() { return strokeOrder; }
public void setStrokeOrder(int order) { this.strokeOrder = order; }
public boolean isValid() { return isValid; }
public void setValid(boolean valid) { isValid = valid; }
@Override
public String toString() {
return "StrokePath{id='" + strokeId + "', points=" + points.size()
+ ", duration=" + getDuration() + "ms"
+ ", text='" + recognizedText + "'}";
}
}