第五次课:抄表业务分析
分类: springboot 专栏: 物联网项目 标签: 抄表业务
2023-06-07 10:02:08 658浏览
抄表业务分析
前言1:先把之前的netty代码优化一下
netty服务端修改,主要是那个编码器和解码器
//给workerGroup 的 EventLoop 对应的管道设置处理器 .childHandler(new ChannelInitializer<SocketChannel>() { //给pipeline 设置处理器 @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); // ChannelOutboundHandler,依照逆序执行 pipeline.addLast("encoder", new NettyMessageEncoder()); // 属于ChannelInboundHandler,依照顺序执行 pipeline.addLast("decoder", new NettyMessageDecoder()); pipeline.addLast(new NettyServerHandler()); } });
自定义编码器-负责写
public class NettyMessageEncoder extends MessageToByteEncoder<String> { @Override protected void encode(ChannelHandlerContext channelHandlerContext, String s, ByteBuf byteBuf) throws Exception { byteBuf.writeBytes(ElectricityMeterUtil.fromHexString(s)); } }
自定义解码器-负责读
@Slf4j public class NettyMessageDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> out) throws Exception { log.info("开始解码:"); byte[] b = new byte[byteBuf.readableBytes()]; //复制内容到字节数组b byteBuf.readBytes(b); //字节数组转字符串 String str = new String(b); String content = ElectricityMeterUtil.getBufHexStr(b); out.add(content); log.info("解码结束!"); } }
前言2:发送指令时把操作类型存入redis
比如:你是进行的拉闸操作还是合闸操作还是查电表操作。
//防止一台设备同时下发多条指令,如果缓存中有记录则表示上次的操作还未完成 String type = stringRedisTemplate.opsForValue().get(InstructConstant.handleInstructs + deviceCode); if(StringUtils.isEmpty(type)){ stringRedisTemplate.opsForValue().set(InstructConstant.handleInstructs+deviceCode, operationType,30, TimeUnit.SECONDS); //后面是发送指令的代码
内置软件烧写电表
初始化:为了方便测试,先用下面的软件初始化充点钱,充点电。两个重要的数据(余额和已用电量)
什么时候抄表
每隔两个小时就去检查下,用netty 去给网关发送查询电表数据的指令(相当于抄表员,要把每次抄表的数据保存到起来)。
/** * @author:xiaojie * @create: 2023-06-06 19:56 * @Description: 抄表定时任务-每两小时抄一次 */ @Slf4j public class ElectricityMeterJob extends QuartzJobBean { @Resource IotDeviceService iotDeviceService; @Resource IotGatewayService gatewayService; @Resource SendInstructsService sendInstructsService; @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { log.info("查出所有的电表设备的余额"); //查出所有的电表设备的余额 List<IotDevice> list = iotDeviceService.list(); for (IotDevice device : list) { //查出网关 IotGateway gatewayVO = gatewayService.getById(device.getGatewayId()); String address = ElectricityMeterUtil.deviceAddressConvert(device.getDeviceCode()); String ip = "/"+gatewayVO.getGatewayIp()+":"+gatewayVO.getGatewayPort(); String instruce = ElectricityMeterUtil.getCompleteInstruct(address, InstructConstant.ELECTRICITY_BALANCE); sendInstructsService.sendInstructs(instruce,ip,device.getDeviceCode(), InstructTypeConstant.E_BALANCE_SETUPA.getValue()); } } }
netty回调
在netty的serverHanlder里的channelRead里面。
客户端收到消息后的回调
//接收到客户端给服务端发的消息 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //可以修改 电表设备的状态 String returnStr=(String) msg; log.info("channelRead:read msg:"+msg); sendInstructsService.callbackInstructResultMsg(returnStr); }
@Override public void callbackInstructResultMsg(String returnStr) { //回调处理 if(StringUtils.isEmpty(returnStr) || !returnStr.startsWith("FE")){ return; } //获取回调设备编号要有好几步骤 String notFEStr = returnStr.substring(returnStr.indexOf("68")+2); String deviceType = notFEStr.startsWith("10")?"w":"e"; //根据设备号找到数据,并更新数据 if("e".equals(deviceType)) {//e就是电表 w就是水表 String addressNo = notFEStr.substring(0,notFEStr.indexOf("68")); //获取设备号 String deviceNo = ElectricityMeterUtil.addressConvertNo(addressNo,true); //获取操作类型 String operationType = stringRedisTemplate.opsForValue().get(InstructConstant.handleInstructs+deviceNo); //删除redis中的数据 stringRedisTemplate.delete(InstructConstant.handleInstructs+deviceNo); //电表操作 handleOperation(deviceNo,operationType,notFEStr); } } private void handleOperation(String deviceNo, String operationType, String notFEStr) { LambdaQueryWrapper<IotDevice> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(IotDevice::getDeviceCode,deviceNo); IotDevice device = deviceMapper.selectOne(wrapper); //合总电闸,拉闸或合闸后修改设备表状态 if(InstructTypeConstant.E_MAIN_GATE_NO.getKey().equals(operationType)){ device.setElectricityStatus(1); deviceMapper.update(device,wrapper); } //拉总电闸 if(InstructTypeConstant.E_MAIN_GATE_OFF.getKey().equals(operationType)){ device.setElectricityStatus(0); deviceMapper.update(device,wrapper); } //获取电表费控数据 if(InstructTypeConstant.E_BALANCE_SETUPA.getKey().equals(operationType)){ getElectricityBalanceInfo(notFEStr,deviceNo); } }
private void getElectricityBalanceInfo(String notFEStr, String deviceNo) { String dataStr = notFEStr.substring(notFEStr.indexOf("81104A1C")+8,notFEStr.length()-4); //剩余金额 String surplusAmount = dataStr.substring(12,20); surplusAmount = ElectricityMeterUtil.deviceAddressConvert(surplusAmount); surplusAmount = ElectricityMeterUtil.dataAreaSubtract33(surplusAmount); log.info("getElectricityBalanceInfo surplusAmount {} {}",surplusAmount); int amount = Integer.parseInt(surplusAmount.replaceAll(" ",""),16); log.info("getElectricityBalanceInfo amount {} {}",amount); String surplusAmountStr = String.valueOf(amount*0.01); log.info("getElectricityBalanceInfo surplusAmount {} {}",surplusAmount,surplusAmountStr); //查询设备信息 LambdaQueryWrapper<IotDevice> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(IotDevice::getDeviceCode,deviceNo); IotDevice vo = deviceMapper.selectOne(wrapper); BigDecimal deviceBalance = vo.getDeviceBalance();//抄表前的剩余金额 BigDecimal deviceSurplus = vo.getDeviceSurplus();//抄表前的剩余电量 //计算使用费用 BigDecimal useBalance = deviceBalance.subtract(new BigDecimal(surplusAmountStr)); log.info("Electricity balance: {}",useBalance); //计算使用量 BigDecimal useSurplus = useBalance.divide(new BigDecimal(1)); //计算设备剩余费用 BigDecimal remnantBalance = deviceBalance.subtract(useBalance); BigDecimal remnantQuantity = deviceSurplus.subtract(useSurplus); IotElectricityUse billElectricityUse = new IotElectricityUse(); billElectricityUse.setDeviceId(vo.getId()); billElectricityUse.setBeforUseBalance(deviceBalance); billElectricityUse.setBeforUseElectricity(deviceSurplus); //账户余额与电表余额对比 if(deviceBalance.compareTo(new BigDecimal(surplusAmountStr))>0){ //使用过电,修改余额和可使用量 vo.setDeviceBalance(remnantBalance); vo.setDeviceSurplus(remnantQuantity); //更新设备金额 deviceMapper.updateById(vo); //添加抄表记录 billElectricityUse.setUseElectricity(useSurplus); billElectricityUse.setUseCost(useBalance); billElectricityUseDao.insert(billElectricityUse); }else{ //没有使用过电 添加抄表记录 billElectricityUse.setUseElectricity(BigDecimal.ZERO); billElectricityUse.setUseCost(BigDecimal.ZERO); billElectricityUseDao.insert(billElectricityUse); } //结算余额小于0,拉闸停电 if(deviceBalance.compareTo(BigDecimal.ZERO)<0 || remnantBalance.compareTo(BigDecimal.ZERO)<0){ log.info("Electricity Balance Not enough"); IotGateway gatewayVO = gatewayMapper.selectById(vo.getGatewayId()); String address = ElectricityMeterUtil.deviceAddressConvert(deviceNo); String newIp = "/"+gatewayVO.getGatewayIp()+":"+gatewayVO.getGatewayPort(); String instructs = ElectricityMeterUtil.getCompleteInstruct(address,InstructConstant.ELECTRICITY_METER_PULL_INSTRUCT); //获取发送指令 sendInstructs(instructs,newIp,deviceNo,InstructTypeConstant.E_MAIN_GATE_OFF.getKey()); log.info("Electricity Balance sendInstructs success"); //修改总闸状态 vo.setElectricityStatus(0); deviceMapper.updateById(vo); }else{ //余额大于0,合闸送电 log.info("Electricity Balance enough"); IotGateway gatewayVO = gatewayMapper.selectById(vo.getGatewayId()); String address = ElectricityMeterUtil.deviceAddressConvert(deviceNo); String newIp = "/"+gatewayVO.getGatewayIp()+":"+gatewayVO.getGatewayPort(); String instructs = ElectricityMeterUtil.getCompleteInstruct(address,InstructConstant.ELECTRICITY_METER_ON_INSTRUCT); //获取发送指令 sendInstructs(instructs,newIp,deviceNo,InstructTypeConstant.E_MAIN_GATE_NO.getKey()); log.info("Electricity Balance sendInstructs success"); //修改总闸状态 vo.setElectricityStatus(1); deviceMapper.updateById(vo); } } /** * 数据转换 * @param aVoltage 元数据 * @param precisionIndex 几位小数 * @return */ private static String dataChange(String aVoltage,int precisionIndex){ String dataBCD = ElectricityMeterUtil.hexAddcharacter(aVoltage," "); String subtract33Data = ElectricityMeterUtil.dataAreaSubtract33(dataBCD); log.info("dataChange {}",subtract33Data); String changeAfterStr = ElectricityMeterUtil.addressConvertNo(subtract33Data,false); Long changeAfterLong = Long.valueOf(changeAfterStr); if(precisionIndex==1){ return String.valueOf(changeAfterLong*0.1); } if(precisionIndex==2){ return String.valueOf(changeAfterLong*0.01); } if(precisionIndex==3){ return String.valueOf(changeAfterLong*0.001); } if(precisionIndex==4){ return String.valueOf(changeAfterLong*0.0001); } return String.valueOf(changeAfterLong); }
回调业务难点
补充 :非spring管理的类怎么拿bean
private static final StringRedisTemplate stringRedisTemplate = SpringUtils.getBean("stringRedisTemplate");
/** * ClassName: SpringUtils * Description: spring工具类 方便在非spring管理环境中获取bean */ @Component public class SpringUtils implements BeanFactoryPostProcessor { /** * Spring应用上下文环境 */ private static ConfigurableListableBeanFactory beanFactory; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } /** * 获取对象 * * @param name * @return Object 一个以所给名字注册的bean的实例 * @throws BeansException */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); } /** * 获取类型为requiredType的对象 * * @param clz * @return * @throws BeansException */ public static <T> T getBean(Class<T> clz) throws BeansException { T result = (T) beanFactory.getBean(clz); return result; } /** * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true * * @param name * @return boolean */ public static boolean containsBean(String name) { return beanFactory.containsBean(name); } /** * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) * * @param name * @return boolean * @throws NoSuchBeanDefinitionException */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return beanFactory.isSingleton(name); } /** * @param name * @return Class 注册对象的类型 * @throws NoSuchBeanDefinitionException */ public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { return beanFactory.getType(name); } /** * 如果给定的bean名字在bean定义中有别名,则返回这些别名 * * @param name * @return * @throws NoSuchBeanDefinitionException */ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return beanFactory.getAliases(name); } /** * 获取aop代理对象 * * @param invoker * @return */ @SuppressWarnings("unchecked") public static <T> T getAopProxy(T invoker) { return (T) AopContext.currentProxy(); } }
老师demo
https://gitee.com/jf3q/school-iot.git
好博客就要一起分享哦!分享海报
此处可发布评论
评论(6)展开评论
周杰伦
能力:10
2023-06-07 16:44:59
学习是每个人终身必不可少的过程。在我们的成长过程中,无论是在学校还是在社会上,我们都需要不断地学习与提升自己,以应对日益复杂和多样化的挑战。学习可以让我们开阔眼界,增长知识,提升技能,培养能力,不仅能满足我们的求知欲,更能让我们更好地适应生活和工作。
周杰伦
能力:10
2023-06-07 16:44:51
学习的过程需要我们具备良好的学习方法和态度。在学习方法方面,我们可以采用分类整理法、归纳演绎法、思维导图等多种有效的学习技巧,以此帮助我们更快更好地理解和掌握知识。同时,我们也需要正确的学习态度,包括认真负责、虚心好学、持之以恒等,这些态度将有利于我们建立正确的学习思想和习惯,进而取得更好的学习效果。
周杰伦
能力:10
2023-06-07 16:44:45
建立良好的学习习惯与方法,不仅让我们在学习过程中事半功倍,更能为我们未来的发展打下坚实的基础。在现代信息化时代,不断学习已成为保持竞争力和获取成功的关键,因此,不论是在职场还是生活中,我们都应该积极发扬学习精神,脚踏实地向前。
周杰伦
能力:10
2023-06-07 16:44:39
总之,学习是一个全面、复杂的过程,需要长期、持续的努力和耐心。只有通过坚定的决心、正确的方法和态度,我们才能在学习中不断进步并成为更好的自己。
点击查看更多评论
展开评论
您可能感兴趣的博客
他的专栏
他感兴趣的技术