第五次课:抄表业务分析
分类: springboot 专栏: 物联网项目 标签: 抄表业务
2023-06-07 10:02:08 898浏览
抄表业务分析
前言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
总之,学习是一个全面、复杂的过程,需要长期、持续的努力和耐心。只有通过坚定的决心、正确的方法和态度,我们才能在学习中不断进步并成为更好的自己。
点击查看更多评论
展开评论
您可能感兴趣的博客
他的专栏
他感兴趣的技术

新业务
springboot学习
ssm框架课
vue学习
【带小白】java基础速成