From 57f074995ed4e6e93c7d22c7a7225b11556dd4b6 Mon Sep 17 00:00:00 2001 From: chenyouting <514333061@qq.com> Date: Mon, 4 Aug 2025 15:35:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E9=80=81=E8=AE=BE=E5=A4=87=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E4=BB=A3=E7=A0=81=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/controller/AppDeviceController.java | 8 + .../app/service/AppDeviceBizService.java | 113 ++++- .../LightingCommandTypeConstants.java | 5 + .../global/mqtt/rule/DeviceBootLogoRule.java | 2 +- .../mqtt/rule/DeviceSendMessageRule.java | 76 ++++ .../src/main/resources/image/background.png | Bin 0 -> 397 bytes .../core/utils/Bitmap80x12Generator.java | 2 +- .../core/utils/ImageToCArrayConverter.java | 2 +- .../core/utils/ImageWithTextGenerate.java | 429 ++++++++++++++++++ .../domain/dto/AppDeviceSendMsgBo.java | 6 + 10 files changed, 621 insertions(+), 22 deletions(-) create mode 100644 fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/DeviceSendMessageRule.java create mode 100644 fys-admin/src/main/resources/image/background.png create mode 100644 fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ImageWithTextGenerate.java diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceController.java index d9217086..1151c88e 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppDeviceController.java @@ -111,6 +111,14 @@ public class AppDeviceController extends BaseController { return toAjax(appDeviceService.sendMessage(bo)); } + /** + * 发送报警信息 + */ + @PostMapping(value = "/sendAlarmMessage") + public R sendAlarmMessage(@RequestBody AppDeviceSendMsgBo bo) { + return toAjax(appDeviceService.sendAlarmMessage(bo)); + } + /** * 上传设备logo图片 */ diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceBizService.java b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceBizService.java index c1063f73..ac36e82f 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceBizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceBizService.java @@ -20,10 +20,7 @@ import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper; import com.fuyuanshen.app.mapper.equipment.APPDeviceMapper; import com.fuyuanshen.common.core.constant.GlobalConstants; import com.fuyuanshen.common.core.exception.ServiceException; -import com.fuyuanshen.common.core.utils.ImageToCArrayConverter; -import com.fuyuanshen.common.core.utils.MapstructUtils; -import com.fuyuanshen.common.core.utils.ObjectUtils; -import com.fuyuanshen.common.core.utils.StringUtils; +import com.fuyuanshen.common.core.utils.*; import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.redis.utils.RedisUtils; @@ -44,9 +41,12 @@ import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.constants.MqttConstants; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.io.InputStream; import java.time.Duration; import java.util.*; @@ -89,25 +89,49 @@ public class AppDeviceBizService { throw new ServiceException("请选择设备"); } for (Long deviceId : deviceIds) { - Device deviceObj = deviceMapper.selectById(deviceId); - if (deviceObj == null) { + Device device = deviceMapper.selectById(deviceId); + if (device == null) { throw new ServiceException("设备不存在" + deviceId); } - byte[] msg = ReliableTextToBitmap.textToBitmapBytes(bo.getSendMsg()); - ArrayList intData = new ArrayList<>(); - intData.add(2); - buildArr(convertHexToDecimal(msg), intData); - Map map = new HashMap<>(); - map.put("instruct", intData); - mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY + deviceObj.getDeviceImei(), 1, JSON.toJSONString(map)); - log.info("发送设备消息:topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY + deviceObj.getDeviceImei(), bo.getSendMsg()); + try { + ClassPathResource resource = new ClassPathResource("image/background.png"); + InputStream inputStream = resource.getInputStream(); + +// String backgroundImagePath = "D:\\background.png"; // 替换为实际背景图片路径 + byte[] largeData = ImageWithTextGenerate.generate160x80ImageWithText2(bo.getSendMsg(), inputStream, 25600); + int[] ints = convertHexToDecimal(largeData); + RedisUtils.setCacheObject(GLOBAL_REDIS_KEY+"app_send_message_data:" + device.getDeviceImei(), Arrays.toString(ints), Duration.ofSeconds(30 * 60L)); + + String data = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+"app_send_message_data:" + device.getDeviceImei()); + + byte[] arr = ImageToCArrayConverter.convertStringToByteArray(data); + byte[] specificChunk = ImageToCArrayConverter.getChunk(arr, 0, 512); + System.out.println("第0块数据大小: " + specificChunk.length + " 字节"); + System.out.println("第0块数据: " + Arrays.toString(specificChunk)); + + ArrayList intData = new ArrayList<>(); + intData.add(6); + intData.add(1); + ImageToCArrayConverter.buildArr(convertHexToDecimal(specificChunk),intData); + intData.add(0); + intData.add(0); + intData.add(0); + intData.add(0); + Map map = new HashMap<>(); + map.put("instruct", intData); + mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY+device.getDeviceImei(), 1 , JSON.toJSONString(map)); + log.info("发送点阵数据到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY+device.getDeviceImei(),JSON.toJSONString(map)); + + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("id", deviceId) + .eq("binding_user_id", AppLoginHelper.getUserId()) + .set("send_msg", bo.getSendMsg()); + deviceMapper.update(updateWrapper); + } catch (Exception e) { + log.info("设备发送信息失败:{}" ,deviceId); + } - UpdateWrapper updateWrapper = new UpdateWrapper<>(); - updateWrapper.eq("id", deviceId) - .eq("binding_user_id", AppLoginHelper.getUserId()) - .set("send_msg", bo.getSendMsg()); - deviceMapper.update(updateWrapper); } return 1; } @@ -390,6 +414,16 @@ public class AppDeviceBizService { } + public static void main(String[] args) throws IOException { + byte[] largeData = ImageToCArrayConverter.convertImageToCArray("E:\\workspace\\demo.png", 160, 80, 25600); + System.out.println("长度:" + largeData.length); + + System.out.println("原始数据大小: " + largeData.length + " 字节"); + + int[] ints = convertHexToDecimal(largeData); + System.out.println("转换后的数据: " + Arrays.toString(ints)); + } + public void uploadDeviceLogo(AppDeviceLogoUploadDto bo) { try { Device device = deviceMapper.selectById(bo.getDeviceId()); @@ -524,4 +558,45 @@ public class AppDeviceBizService { } return RedisUtils.getCacheObject("device:location:" + devices.get(0).getDeviceImei()); } + + public int sendAlarmMessage(AppDeviceSendMsgBo bo) { + try { + List deviceIds = bo.getDeviceIds(); + if (deviceIds == null || deviceIds.isEmpty()) { + throw new ServiceException("请选择设备"); + } + for (Long deviceId : deviceIds) { + Device device = deviceMapper.selectById(deviceId); + if (device == null) { + throw new ServiceException("设备不存在" + deviceId); + } + + try { + ArrayList intData = new ArrayList<>(); + intData.add(7); + intData.add(Integer.parseInt(bo.getInstructValue())); + intData.add(0); + intData.add(0); + intData.add(0); + intData.add(0); + Map map = new HashMap<>(); + map.put("instruct", intData); + mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY+device.getDeviceImei(), 1 , JSON.toJSONString(map)); + log.info("发送点阵数据到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY+device.getDeviceImei(),JSON.toJSONString(map)); + + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("id", deviceId) + .eq("binding_user_id", AppLoginHelper.getUserId()) + .set("send_msg", bo.getSendMsg()); + deviceMapper.update(updateWrapper); + } catch (Exception e) { + log.info("设备发送信息失败:{}" ,deviceId); + } + + } + } catch (Exception e){ + e.printStackTrace(); + } + return 1; + } } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/LightingCommandTypeConstants.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/LightingCommandTypeConstants.java index 64c84494..c0f1ee75 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/LightingCommandTypeConstants.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/LightingCommandTypeConstants.java @@ -30,6 +30,11 @@ public class LightingCommandTypeConstants { * 主灯亮度 (Main Light Brightness) */ public static final String MAIN_LIGHT_BRIGHTNESS = "Light_5"; + + /** + * 设备发送消息 + */ + public static final String SEND_MESSAGE = "Light_6"; /** * 定位数据 (Location Data) diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/DeviceBootLogoRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/DeviceBootLogoRule.java index 8b83e0f0..af5bc109 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/DeviceBootLogoRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/DeviceBootLogoRule.java @@ -67,7 +67,7 @@ public class DeviceBootLogoRule implements MqttMessageRule { map.put("instruct", intData); mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), 1, JsonUtils.toJsonString(map)); - log.info("发送人员信息点阵数据到设备消息=>topic:{},payload:{}", + log.info("发送开机LOGO点阵数据到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), JsonUtils.toJsonString(map)); } catch (Exception e) { diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/DeviceSendMessageRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/DeviceSendMessageRule.java new file mode 100644 index 00000000..fab1fdee --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/DeviceSendMessageRule.java @@ -0,0 +1,76 @@ +package com.fuyuanshen.global.mqtt.rule; + +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.utils.ImageToCArrayConverter; +import com.fuyuanshen.common.core.utils.StringUtils; +import com.fuyuanshen.common.json.utils.JsonUtils; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.MqttMessageRule; +import com.fuyuanshen.global.mqtt.base.MqttRuleContext; +import com.fuyuanshen.global.mqtt.config.MqttGateway; +import com.fuyuanshen.global.mqtt.constants.LightingCommandTypeConstants; +import com.fuyuanshen.global.mqtt.constants.MqttConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static com.fuyuanshen.common.core.utils.ImageToCArrayConverter.convertHexToDecimal; + +/** + * 人员信息命令处理 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class DeviceSendMessageRule implements MqttMessageRule { + + private final MqttGateway mqttGateway; + + @Override + public String getCommandType() { + return LightingCommandTypeConstants.SEND_MESSAGE; + } + + @Override + public void execute(MqttRuleContext context) { + try { + Byte val2 = (Byte) context.getConvertArr()[1]; + if (val2 == 100) { + return; + } + + String data = RedisUtils.getCacheObject(GlobalConstants.GLOBAL_REDIS_KEY+"app_send_message_data:" + context.getDeviceImei()); + if (StringUtils.isEmpty(data)) { + return; + } + + byte[] arr = ImageToCArrayConverter.convertStringToByteArray(data); + byte[] specificChunk = ImageToCArrayConverter.getChunk(arr, (val2 - 1), 512); + System.out.println("第" + val2 + "块数据大小: " + specificChunk.length + " 字节"); +// System.out.println("第" + val2 + "块数据: " + Arrays.toString(specificChunk)); + + ArrayList intData = new ArrayList<>(); + intData.add(6); + intData.add((int) val2); + ImageToCArrayConverter.buildArr(convertHexToDecimal(specificChunk), intData); + intData.add(0); + intData.add(0); + intData.add(0); + intData.add(0); + + Map map = new HashMap<>(); + map.put("instruct", intData); + + mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), 1, JsonUtils.toJsonString(map)); + log.info("发送设备信息数据到设备消息=>topic:{},payload:{}", + MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), + JsonUtils.toJsonString(map)); + } catch (Exception e) { + log.error("处理人员信息命令时出错", e); + } + } +} diff --git a/fys-admin/src/main/resources/image/background.png b/fys-admin/src/main/resources/image/background.png new file mode 100644 index 0000000000000000000000000000000000000000..1670e7f0d6771452a2584551104143b0df20bea6 GIT binary patch literal 397 zcmeAS@N?(olHy`uVBq!ia0vp^3xGI)g&9cZ6eqI-DaPU;cPGZ1Cw1z99L@rd$YKTt zZeb8+WSBKa0w`z@;1lA?#ls(EC9tl8ZEXkJgN;nhN!%?-+^ag+987r}O?k@VIZER> z5|UEp&YQogoi)-@K>qE@DL}p7JY5_^D&{1oEHFxtF)f%Nbz;R558lN~#1HU<^ye&S zYMfKNcmdm{wOk9DDtUPBEaF#SOY>da1!TAW6<}7L9(?k^GOLb8KK2T|6%GQaY)1cE zB~Mg4N;YX2Pk72dHMYZGf>E&>FVN^K2LhShaWN7T7aZf&lDrYaZ5Z%Jbt=!DXRL-3 zGG(W>uaelhF6gjLUudEHg2lx-RZJCAi=OUcx|$#CePH#g{co7ql-N}yo0FL&wuRXL zap;Lk;z>#HnD|m`xBnC`CEk?cxw@`%ypoP5wEhtbxbCp%;EKlP3l0ot-cAmD(tHgR O;0&IwelF{r5}E+`?~7sp literal 0 HcmV?d00001 diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/Bitmap80x12Generator.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/Bitmap80x12Generator.java index 5c6996eb..5a678e55 100644 --- a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/Bitmap80x12Generator.java +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/Bitmap80x12Generator.java @@ -242,7 +242,7 @@ public class Bitmap80x12Generator { return byteListToArray(byteList); } - private static byte[] byteListToArray(List byteList) { + public static byte[] byteListToArray(List byteList) { byte[] result = new byte[byteList.size()]; for (int i = 0; i < byteList.size(); i++) { result[i] = byteList.get(i); diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ImageToCArrayConverter.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ImageToCArrayConverter.java index 45fea748..8823ad08 100644 --- a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ImageToCArrayConverter.java +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ImageToCArrayConverter.java @@ -33,7 +33,7 @@ public class ImageToCArrayConverter { public static void main(String[] args) throws IOException { - byte[] largeData = convertImageToCArray("E:\\workspace\\6170_强光_160_80_2.jpg", 160, 80,25600); + byte[] largeData = convertImageToCArray("E:\\workspace\\demo.png", 160, 80,25600); System.out.println("长度:"+largeData.length); System.out.println("原始数据大小: " + largeData.length + " 字节"); diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ImageWithTextGenerate.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ImageWithTextGenerate.java new file mode 100644 index 00000000..dafb9a69 --- /dev/null +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/ImageWithTextGenerate.java @@ -0,0 +1,429 @@ +package com.fuyuanshen.common.core.utils; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +public class ImageWithTextGenerate { + + /** + * 生成160*80画布,嵌入背景图片并居中显示文字(支持自动换行),输出RGB565格式数据 + * + * @param text 要显示的文字 + * @param fixedLength 固定输出长度(字节数) + * @return RGB565格式的图像数据 + */ + public static byte[] generate160x80ImageWithText2(String text, InputStream backgroundImageInputStream, int fixedLength) throws IOException { + // 创建160*80的图像 + BufferedImage image = new BufferedImage(160, 80, BufferedImage.TYPE_INT_RGB); + Graphics2D g = image.createGraphics(); + + // 绘制白色背景 + g.setColor(Color.WHITE); + g.fillRect(0, 0, 160, 80); + + // 如果提供了背景图片,则绘制背景 + if (backgroundImageInputStream != null ) { + BufferedImage backgroundImage = ImageIO.read(backgroundImageInputStream); + // 缩放并绘制背景图片以适应160*80画布 + g.drawImage(backgroundImage, 0, 0, 160, 80, null); + } + + // 设置文字属性 + Font font = new Font("宋体", Font.PLAIN, 12); // 可根据需要调整字体大小 + g.setFont(font); + g.setColor(new Color(255, 255, 255, (int)(0.6 * 255))); + + // 关闭抗锯齿以获得清晰的点阵效果 + g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + g.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + + // 获取字体度量信息 + FontMetrics metrics = g.getFontMetrics(); + int lineHeight = metrics.getHeight(); + + // 文本换行处理 + ArrayList lines = wrapText(text, metrics, 120); // 160为画布宽度 + + // 计算垂直居中起始位置 + int totalTextHeight = lines.size() * lineHeight; + int startY = (80 - totalTextHeight) / 2 + metrics.getAscent(); + + // 绘制每一行文字 + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + int lineWidth = metrics.stringWidth(line); + int x = (160 - lineWidth) / 2; // 水平居中 + int y = startY + i * lineHeight; + g.drawString(line, x, y); + } + + g.dispose(); + + // 转换像素数据为RGB565格式(高位在前) + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + for (int yCoord = 0; yCoord < 80; yCoord++) { + for (int xCoord = 0; xCoord < 160; xCoord++) { + int rgb = image.getRGB(xCoord, yCoord); + + // 提取RGB分量 + int r = (rgb >> 16) & 0xFF; + int g1 = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 转换为RGB565(5位红,6位绿,5位蓝) + int r5 = (r >> 3) & 0x1F; + int g6 = (g1 >> 2) & 0x3F; + int b5 = (b >> 3) & 0x1F; + + // 组合为16位值 + int rgb565 = (r5 << 11) | (g6 << 5) | b5; + + // 高位在前(大端序)写入字节 + byteStream.write((rgb565 >> 8) & 0xFF); // 高字节 + byteStream.write(rgb565 & 0xFF); // 低字节 + } + } + + // 调整到固定长度 + byte[] rawData = byteStream.toByteArray(); + byte[] result = new byte[fixedLength]; + int copyLength = Math.min(rawData.length, fixedLength); + System.arraycopy(rawData, 0, result, 0, copyLength); + return result; + } + /** + * 生成160*80画布,嵌入背景图片并居中显示文字(支持自动换行),输出RGB565格式数据 + * + * @param text 要显示的文字 + * @param backgroundImagePath 背景图片路径 + * @param fixedLength 固定输出长度(字节数) + * @return RGB565格式的图像数据 + */ + public static byte[] generate160x80ImageWithText(String text, String backgroundImagePath, int fixedLength) throws IOException { + // 创建160*80的图像 + BufferedImage image = new BufferedImage(160, 80, BufferedImage.TYPE_INT_RGB); + Graphics2D g = image.createGraphics(); + + // 绘制白色背景 + g.setColor(Color.WHITE); + g.fillRect(0, 0, 160, 80); + + // 如果提供了背景图片,则绘制背景 + if (backgroundImagePath != null && !backgroundImagePath.isEmpty()) { + File backgroundFile = new File(backgroundImagePath); + if (backgroundFile.exists()) { + BufferedImage backgroundImage = ImageIO.read(backgroundFile); + // 缩放并绘制背景图片以适应160*80画布 + g.drawImage(backgroundImage, 0, 0, 160, 80, null); + } + } + + // 设置文字属性 + Font font = new Font("宋体", Font.PLAIN, 12); // 可根据需要调整字体大小 + g.setFont(font); + g.setColor(new Color(255, 255, 255, (int)(0.6 * 255))); + + // 关闭抗锯齿以获得清晰的点阵效果 + g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + g.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + + // 获取字体度量信息 + FontMetrics metrics = g.getFontMetrics(); + int lineHeight = metrics.getHeight(); + + // 文本换行处理 + ArrayList lines = wrapText(text, metrics, 120); // 160为画布宽度 + + // 计算垂直居中起始位置 + int totalTextHeight = lines.size() * lineHeight; + int startY = (80 - totalTextHeight) / 2 + metrics.getAscent(); + + // 绘制每一行文字 + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + int lineWidth = metrics.stringWidth(line); + int x = (160 - lineWidth) / 2; // 水平居中 + int y = startY + i * lineHeight; + g.drawString(line, x, y); + } + + g.dispose(); + + // 转换像素数据为RGB565格式(高位在前) + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + for (int yCoord = 0; yCoord < 80; yCoord++) { + for (int xCoord = 0; xCoord < 160; xCoord++) { + int rgb = image.getRGB(xCoord, yCoord); + + // 提取RGB分量 + int r = (rgb >> 16) & 0xFF; + int g1 = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 转换为RGB565(5位红,6位绿,5位蓝) + int r5 = (r >> 3) & 0x1F; + int g6 = (g1 >> 2) & 0x3F; + int b5 = (b >> 3) & 0x1F; + + // 组合为16位值 + int rgb565 = (r5 << 11) | (g6 << 5) | b5; + + // 高位在前(大端序)写入字节 + byteStream.write((rgb565 >> 8) & 0xFF); // 高字节 + byteStream.write(rgb565 & 0xFF); // 低字节 + } + } + + // 调整到固定长度 + byte[] rawData = byteStream.toByteArray(); + byte[] result = new byte[fixedLength]; + int copyLength = Math.min(rawData.length, fixedLength); + System.arraycopy(rawData, 0, result, 0, copyLength); + return result; + } + + /** + * 文本换行处理 + * + * @param text 原始文本 + * @param metrics 字体度量信息 + * @param maxWidth 最大宽度 + * @return 换行后的文本行列表 + */ + private static ArrayList wrapText(String text, FontMetrics metrics, int maxWidth) { + ArrayList lines = new ArrayList<>(); + String[] paragraphs = text.split("\n"); + + for (String paragraph : paragraphs) { + String[] words = paragraph.split("(?<=\\S)(?=\\s)|(?<=\\s)(?=\\S)"); + StringBuilder line = new StringBuilder(); + + for (String word : words) { + String testLine = line.toString() + word; + int lineWidth = metrics.stringWidth(testLine); + + if (lineWidth <= maxWidth) { + line.append(word); + } else { + if (line.length() > 0) { + lines.add(line.toString()); + line = new StringBuilder(word); + } else { + // 单个词就超过宽度,需要进一步拆分 + lines.addAll(wrapWord(word, metrics, maxWidth)); + } + } + } + + if (line.length() > 0) { + lines.add(line.toString()); + } + } + + // 限制最大行数以适应80像素高度 + if (lines.size() > 6) { // 假设每行最多13像素高,80/13约等于6 + return (ArrayList) lines.subList(0, 6); + } + + return lines; + } + + /** + * 对单个超长词进行拆分 + * + * @param word 单词 + * @param metrics 字体度量信息 + * @param maxWidth 最大宽度 + * @return 拆分后的词列表 + */ + private static ArrayList wrapWord(String word, FontMetrics metrics, int maxWidth) { + ArrayList result = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + + for (char c : word.toCharArray()) { + String testStr = current.toString() + c; + if (metrics.stringWidth(testStr) <= maxWidth) { + current.append(c); + } else { + if (current.length() > 0) { + result.add(current.toString()); + } + current = new StringBuilder(String.valueOf(c)); + } + } + + if (current.length() > 0) { + result.add(current.toString()); + } + + return result; + } + + /** + * 生成160*80画布,嵌入背景图片并居中显示文字,输出RGB565格式数据(支持InputStream) + * + * @param text 要显示的文字 + * @param backgroundImageInputStream 背景图片输入流 + * @param fixedLength 固定输出长度(字节数) + * @return RGB565格式的图像数据 + */ + public static byte[] generate160x80ImageWithText(String text, InputStream backgroundImageInputStream, int fixedLength) throws IOException { + // 创建160*80的图像 + BufferedImage image = new BufferedImage(160, 80, BufferedImage.TYPE_INT_RGB); + Graphics2D g = image.createGraphics(); + + // 绘制白色背景 + g.setColor(Color.WHITE); + g.fillRect(0, 0, 160, 80); + + // 如果提供了背景图片,则绘制背景 + if (backgroundImageInputStream != null) { + BufferedImage backgroundImage = ImageIO.read(backgroundImageInputStream); + // 缩放并绘制背景图片以适应160*80画布 + g.drawImage(backgroundImage, 0, 0, 160, 80, null); + } + + // 设置文字属性 + Font font = new Font("宋体", Font.PLAIN, 16); // 可根据需要调整字体大小 + g.setFont(font); + g.setColor(Color.BLACK); + + // 关闭抗锯齿以获得清晰的点阵效果 + g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + g.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + + // 获取字体度量信息 + FontMetrics metrics = g.getFontMetrics(); + + // 计算文字的宽度和高度 + int textWidth = metrics.stringWidth(text); + int textHeight = metrics.getHeight(); + + // 计算居中位置 + int x = (160 - textWidth) / 2; // 水平居中 + int y = (80 - textHeight) / 2 + metrics.getAscent(); // 垂直居中 + + // 绘制文字 + g.drawString(text, x, y); + + g.dispose(); + + // 转换像素数据为RGB565格式(高位在前) + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + for (int yCoord = 0; yCoord < 80; yCoord++) { + for (int xCoord = 0; xCoord < 160; xCoord++) { + int rgb = image.getRGB(xCoord, yCoord); + + // 提取RGB分量 + int r = (rgb >> 16) & 0xFF; + int g1 = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 转换为RGB565(5位红,6位绿,5位蓝) + int r5 = (r >> 3) & 0x1F; + int g6 = (g1 >> 2) & 0x3F; + int b5 = (b >> 3) & 0x1F; + + // 组合为16位值 + int rgb565 = (r5 << 11) | (g6 << 5) | b5; + + // 高位在前(大端序)写入字节 + byteStream.write((rgb565 >> 8) & 0xFF); // 高字节 + byteStream.write(rgb565 & 0xFF); // 低字节 + } + } + + // 调整到固定长度 + byte[] rawData = byteStream.toByteArray(); + byte[] result = new byte[fixedLength]; + int copyLength = Math.min(rawData.length, fixedLength); + System.arraycopy(rawData, 0, result, 0, copyLength); + return result; + } + /** + * 将RGB565格式的字节数组转换为BufferedImage + * + * @param data RGB565格式的数据 + * @param height 图像高度 + * @param width 图像宽度 + * @return 转换后的BufferedImage + */ + public static BufferedImage convertByteArrayToImage(byte[] data, int height, int width) { + if (data == null || data.length == 0) { + return new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); + } + + // 创建图像 + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + + // 处理RGB565数据 + int dataIndex = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // 每个像素占2个字节 + if (dataIndex + 1 >= data.length) { + return image; + } + + // 读取两个字节组成RGB565值 + int highByte = data[dataIndex++] & 0xFF; + int lowByte = data[dataIndex++] & 0xFF; + int rgb565 = (highByte << 8) | lowByte; + + // 将RGB565转换为RGB888 + int r = ((rgb565 >> 11) & 0x1F) << 3; + int g = ((rgb565 >> 5) & 0x3F) << 2; + int b = (rgb565 & 0x1F) << 3; + + int rgb = (r << 16) | (g << 8) | b; + image.setRGB(x, y, rgb); + } + } + + return image; + } + + + public static void main(String[] args) throws IOException { + // ... 原有代码 ... + + // 测试生成160*80画布,嵌入背景图片并居中显示文字 +// String text = "现在危险,停止救援紧急撤离至安全区域"; + String text = "现在危险,停止救援,紧急撤离至安全区域!"; + String backgroundImagePath = "D:\\background.png"; // 替换为实际背景图片路径 + byte[] imageData = generate160x80ImageWithText(text, backgroundImagePath, 25600); + + System.out.println("生成的160*80 RGB565图像数据:"); + System.out.println("数据长度: " + imageData.length + " 字节"); + // 生成预览图片 + // 生成预览图片 + BufferedImage image160x80 = convertByteArrayToImage(imageData, 80, 160); + ImageIO.write(image160x80, "PNG", new File("D:\\bitmap_160x80_preview.png")); +// System.out.println("成功生成160*80预览图片: D:\\bitmap_160x80_preview.png"); + // 转换为十进制数组 +// int[] decimalArray = convertHexToDecimal(imageData); +// System.out.println("生成的十进制数据(前50个):"); +// System.out.println(Arrays.toString(Arrays.copyOf(decimalArray, Math.min(50, decimalArray.length)))); +// +// // 将数据分割成512字节的块 +// List chunks = splitByteArrayIntoChunks(imageData, 512); +// printChunkInfo(chunks); +// +// // 示例:获取特定块的数据 +// byte[] specificChunk = getChunk(imageData, 0, 512); // 获取第1块(索引0) +// System.out.println("第1块数据大小: " + specificChunk.length + " 字节"); + } + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/AppDeviceSendMsgBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/AppDeviceSendMsgBo.java index 40caaadf..1b57ec6e 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/AppDeviceSendMsgBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/AppDeviceSendMsgBo.java @@ -13,4 +13,10 @@ public class AppDeviceSendMsgBo { private String sendMsg; private List deviceIds; + + /** + * 下发指令 + */ + private String instructValue; + }